본문으로 건너뛰기

Platform RAG 아키텍처

ADR 0021 에서 확정된 "전 tenant 공용 공공 판례·법령 RAG 층" 의 구현 SSoT. 설계 사유·대안 검토는 ADR, 아래는 런타임 구조·파일 위치·일일 운영 흐름 만 다룬다.

Tenant-격리 RAG (ADR 0015 · ADR 0017) 와 병렬 · 독립 으로 동작한다. 두 층 모두 AI 프롬프트에 동시 주입되지만, Firestore 경로·쓰기 권한·리스크 프로파일이 다르다.

두 RAG 층 비교

Tenant RAGPlatform RAG
출처사무소 사적 OCR 서면 (ADR 0015 → 0017)공공 판례·법령 (법제처 · 법고을 · 대법원)
Firestore 경로tenants/{tid}/legacyDocuments/{id}publicDocuments/{docId} (tenant 경로 밖)
쓰기 권한tenant staff (OCR 승인 플로우)masterAdmin only (Admin SDK + rules)
읽기 범위해당 tenant AI 만모든 인증 tenant
임베딩 격리findNearest 가 tenant 경로로 시작 → 아키텍처상 누출 불가격리 대상 아님 (공공 데이터)
Cross-tenant 리스크있음 (엄격 차단 · ADR 0015 핵심)없음 (원천적으로 공공)

1. Firestore 스키마

publicDocuments/{docId} (tenant 읽기 · masterAdmin 쓰기)
sourceType: "court_precedent" | "statute" | "admin_rule" | "commentary"
origin: "beopgoeul_lx" | "law_go_kr" | "scourt_openapi" | "aihub_71723" | "manual"
title: string
content: string (임베딩 소스 · stripLawGoKrHtml 정제)
sourceUrl?: string (원본 링크)
tags: string[]
precedentMeta?: { court, caseNumber, decisionDate, caseName, holdingSummary, citedStatutes }
statuteMeta?: { lawName, articleNumber, effectiveDate, status }
embedding?: { vector[768], modelVersion, dims, generatedAtIso, chunkCount }
embeddedAt: Timestamp | null
embeddingError: string | null
uploadedByEmail: string
linkedRawDocId: string | null (publicDocumentsRaw 연결키)
createdAt: Timestamp
updatedAt: Timestamp

publicDocumentsRaw/{rawId} (masterAdmin 읽기 · Admin SDK 만 쓰기)
source: "law_go_kr" | ...
targetType: "prec" | "law"
nativeId: string (법제처 판례일련번호 등)
fetchedAtIso: string
raw: object (API 원본 JSON — LLM/임베딩 마이그레이션 대비)
linkedPublicDocId: string
schemaVersion: number
createdAt: Timestamp

publicDocumentsRaw재파싱 복구용. 파싱 규칙 · HTML 정제 로직 · 신규 필드가 바뀌어도 원본이 있으면 네트워크 재호출 없이 publicDocuments 전부 재구축 가능 (scripts/rebuild-public-docs-from-raw.ts).

2. Firestore rules

firestore.rules 관련 블록:

match /publicDocuments/{docId} {
allow read: if isAuthenticated();
allow write: if isAuthenticated() &&
exists(/databases/$(database)/documents/masterAdmins/$(request.auth.token.email));
}

match /publicDocumentsRaw/{rawId} {
allow read: if isAuthenticated() &&
exists(/databases/$(database)/documents/masterAdmins/$(request.auth.token.email));
allow write: if false; // Admin SDK 만
}

match /publicRagScheduleLog/{docId} {
allow read: if isAuthenticated() &&
exists(/databases/$(database)/documents/masterAdmins/$(request.auth.token.email));
allow write: if false; // Cloud Function Admin SDK 만
}

firestore-rules-invariants.test.ts 가 이 블록들을 불변조건으로 assert — 누군가 실수로 write 권한을 tenant 에 열면 빌드 실패.

3. 데이터 수집 파이프라인

[법제처 API] ──fetch──▶ data/public-rag-raw/by-case/{id}.json
│ (로컬 · gitignore)

scripts/verify-downloaded-data.ts (품질 게이트)


[로컬 파일] ──upload──▶ publicDocumentsRaw/{rawId} + publicDocuments/{docId}


functions/onPublicDocumentCreated (Vertex AI 임베딩)


publicDocuments.embedding.vector[768]


findNearestPublic (COSINE, ops + tenant 검색 경로)

3.1 수집 트리거 3 종

  1. ops 수동 1회성apps/ops/app/(ops)/public-rag/fetch 버튼
    • fetchAndIngestLawGoKr onCall Function — 쿼리·display 지정 단건 수집
    • 소규모 시험·즉시 갱신 필요 시
  2. 스크립트 bulkscripts/download-law-go-kr-bulk.ts + upload-public-raw-to-firestore.ts
  3. Cloud Scheduler 증분scheduledFetchLawGoKr (매일 02:00 KST)
    • core 21 카테고리 · page 1 (display=20) 자동 순회
    • 멱등 (court + caseNumber 중복 skip) → 신규만 embedding 트리거
    • 결과 publicRagScheduleLog/{YYYY-MM-DD} 로 집계

3.2 멱등 키

유형멱등 키
판례precedentMeta.court + precedentMeta.caseNumber
법령 조문statuteMeta.lawName + statuteMeta.articleNumber
rawsource + nativeId

4. 임베딩 파이프라인

Cloud Function: functions/src/on-public-document-created.ts

onCreate trigger
├─ prepareEmbedInput (ADR 0015 와 동일 정제 — apps 측 논리 복제)
│ HTML strip · [ \t]+ → " " · \n{3,} → \n\n · .trim()
├─ Vertex AI text-embedding-004 @ us-central1
│ REST API + GoogleAuth ADC · content 상한 2000 char
├─ 다중 chunk → meanPoolVectors 평균 풀링 → 768-dim 단일 벡터
├─ publicDocuments.embedding.{vector, modelVersion, dims} update
├─ 실패 시 embeddingError 기록 (재시도 UI 에서 복구)
└─ embeddedAt: serverTimestamp (검색 가능 게이트)

관련 상수:

  • 모델: text-embedding-004 (upgrade 시 scripts/reembed-public-docs.ts --model-version-mismatch 로 전 문서 재처리)
  • 차원: 768
  • 거리 메트릭: COSINE

5. 검색 경로

5.1 Server 측

  • apps/web/app/(workspace)/cases/_actions/find-related-precedents-action.ts — 에디터 사이드바용 (사건 컨텍스트 기반 자동 쿼리)
  • apps/web/app/(workspace)/library/_actions/search-library-action.ts — /library 독립 검색 (structured 필터 + keyword)
  • apps/web/app/(workspace)/library/_actions/chat-library-action.ts — /library AI 대화 (RAG + Gemini)
  • functions/src/ops-generate-test-doc.ts — masterAdmin 초안 생성 시뮬레이션
  • functions/src/ops-rag-chat.ts — masterAdmin 법률 리서치 대화

공통 규칙: findNearestPublicpublicDocuments 루트에서 직접 호출. tenant path 하위가 아니므로 guardedFindNearest 의 테넌트 경로 검증 대상 아님. 대신 Firestore rules 읽기 권한이 경계 역할 (인증된 tenant 만 read).

5.2 프롬프트 병렬 주입

AI 다듬기 · RAG 답변 프롬프트는 tenant RAG 와 Platform RAG 를 라벨로 구분 하여 동시에 전달:

[내 사무실 과거 사건]
{tenant snippet #1}
{tenant snippet #2}

[공공 판례·법령]
[1] 대법원 2024다12345 (2024-03-15): {snippet}
[2] 민법 제750조: {snippet}

6. 운영 관찰성 (ops 콘솔)

apps/ops/app/(ops)/public-rag/page.tsx 의 패널:

패널역할
PublicRagStatsCard총 문서 · 임베딩 성공률 · origin/sourceType 분포 · 최근 7일·30일 업로드 추이
PublicRagTestPanel자연어 쿼리 → findNearest 결과 상위 K 건 표시 (품질 회귀 감지)
PublicRagFailurePanelembeddingError != null 문서 리스트 + 단건 retryPublicDocEmbedding
PublicRagFetchPanel수동 쿼리 실시간 수집 (fetchAndIngestLawGoKr)
PublicRagDocGenTestPanel사건 컨텍스트 → RAG + Gemini 초안 시뮬레이션
PublicRagChatPanelmasterAdmin AI 법률 리서치 대화

7. 마이그레이션 시나리오

7.1 파싱 규칙 변경 (HTML 정제 강화 등)

# 원본은 publicDocumentsRaw 에 그대로 보존 → 네트워크 재호출 없이 재파싱
NODE_PATH=apps/web/node_modules npx tsx \
scripts/rebuild-public-docs-from-raw.ts --dry-run

content / title / precedentMeta 만 update — embedding·createdAt 보존. content 가 실제로 바뀌면 이후 7.2 재임베딩 필요.

7.2 임베딩 모델 업그레이드 (e.g. text-embedding-004 → 005)

# 1. 모델 상수 변경: functions/src/on-public-document-created.ts + scripts/reembed-public-docs.ts
# 2. 전 문서 재임베딩
NODE_PATH=apps/web/node_modules npx tsx \
scripts/reembed-public-docs.ts --model-version-mismatch

체크포인트 + rate limit 보호. --dry-run 으로 대상 수 확인 후 실제 실행.

7.3 임베딩 실패 누적 배치 재시도

ops 콘솔 PublicRagFailurePanel 은 단건 retryPublicDocEmbedding 호출. 수십 건 이상 쌓이면:

npx tsx scripts/reembed-public-docs.ts --only-failed

7.4 Firestore 데이터 유실

  • data/public-rag-raw/ 로컬 백업이 있다면 upload-public-raw-to-firestore.ts 재실행
  • 양방향이라 publicDocumentsRaw 를 export 해도 복구 가능 (export 스크립트는 필요 시 추가)

8. 파일 위치 한눈에

Cloud Functions

  • 임베딩 트리거 — functions/src/on-public-document-created.ts
  • 단건 재시도 — functions/src/retry-public-doc-embedding.ts
  • 수동 수집 — functions/src/fetch-law-go-kr.ts
  • 증분 스케줄 — functions/src/scheduled-fetch-law-go-kr.ts
  • ops 초안 생성 — functions/src/ops-generate-test-doc.ts
  • ops RAG 대화 — functions/src/ops-rag-chat.ts
  • 공용 어댑터 — functions/src/law-go-kr-adapter.ts

Server Actions (web)

  • 에디터 사이드바 검색 — apps/web/app/(workspace)/cases/_actions/find-related-precedents-action.ts
  • /library 검색 — apps/web/app/(workspace)/library/_actions/search-library-action.ts
  • /library 대화 — apps/web/app/(workspace)/library/_actions/chat-library-action.ts

Scripts

  • 판례 bulk 다운로드 — scripts/download-law-go-kr-bulk.ts
  • 법령 조문 다운로드 — scripts/download-law-go-kr-statutes.ts
  • 카테고리 매니페스트 — scripts/law-go-kr-categories.ts
  • 법령 매니페스트 — scripts/law-go-kr-statute-list.ts
  • 원본 업로드 — scripts/upload-public-raw-to-firestore.ts
  • 법령 업로드 — scripts/upload-statutes-to-firestore.ts
  • 검증 — scripts/verify-downloaded-data.ts
  • 재구축 (파싱 변경) — scripts/rebuild-public-docs-from-raw.ts
  • 재임베딩 — scripts/reembed-public-docs.ts
  • 공용 어댑터 — scripts/lib/law-go-kr-convert.ts

ops UI

  • 페이지 — apps/ops/app/(ops)/public-rag/page.tsx
  • 통계 카드 — apps/ops/app/(ops)/public-rag/_components/PublicRagStatsCard.tsx
  • 통계 집계 — apps/ops/app/(ops)/public-rag/_lib/public-rag-stats.ts
  • 어댑터 — apps/ops/app/(ops)/public-rag/_lib/adapter-law-go-kr.ts

문서

  • ADR — apps/docs/content/product/decisions/0021-platform-rag.md
  • 이 문서 (런타임) — apps/docs/content/architecture/platform-rag.md
  • bulk 운영 절차 — apps/docs/content/operations/public-rag-bulk-ingestion.md

9. 비용·규모

초기 시드 (Pack 1 민사 채권):

  • 판례 ~4,000 건 (실측 4,054 · 43MB)
  • 법령 조문 ~3,600 건 (민법 1337 + 상법 1308 + 기타 · ~20MB)
  • 임베딩 호출: 7,600 × text-embedding-004 ≈ $0.2 (1회성)
  • Firestore 스토리지: ~70MB raw + ~70MB publicDocuments ≈ 무시

일일 증분 (Cloud Scheduler):

  • 21 카테고리 × page 1 × 20건 = 420 API 호출 (법제처 무료)
  • 신규 ≤ 20건/일 예상 → 임베딩 ~$0.001/일

10. 다음 단계

  • AI Hub 71723 어댑터 — 25만 건 판례 데이터셋 포맷 파서
  • Citation 번호 통합 — tenant RAG + Platform RAG 인용을 단일 [1][2] 체계로
  • 품질 모니터링 — Platform RAG 인용이 실제 AI 답변에 얼마나 반영되는지 측정 (샘플링 + 수동 평가)
  • 저작권 검사 UIcommentary 유형 업로드 시 저작권 범위 체크리스트 (ADR 0021 Minority Report P7)