본문으로 건너뛰기

ADR 0041 — AI 서류 생성 두 RAG 자동 결합: Platform RAG + Tenant 사무실 기억

날짜: 2026-05-08 상태: Accepted (1.1 amendment) Tier: A (Architecture · AI 서류 생성 코어) 적용 범위: apps/web/app/(workspace)/legacy/_actions/legacy-search-actions.ts (getRagContextForDocGenAction) · apps/web/lib/ai/client.ts (generateLegalDoc) · apps/web/app/(workspace)/docs/generate/_lib/useDocGenerate.ts

결정문

getRagContextForDocGenAction 서버 액션에서 Tenant RAG (legacyDocuments findNearest 의미 검색) + Platform RAG (publicDocuments findNearest 판례·법령) 를 병렬 호출하여 두 검색 결과를 하나의 RAG context object 로 반환한다. 클라이언트 generateLegalDoc 의 시그니처를 확장해 두 RAG 결과를 동일 LLM prompt 의 systemInstruction 에 자동 주입한다. citation marker 자동 삽입 + citation-existence 환각 검증 inline. 변호사가 별도 패널로 수동 조회·copy-paste 하던 흐름을 폐기한다.

1.0 → 1.1 결정문 정정

1.0 결정문 (오류): 결합 위치를 generateDocxAction 으로 명시.

실측 (코드 read 후):

  • generateDocxAction템플릿 치환만 수행 (AI 호출 없음). 서버 액션이 docx 템플릿 + 데이터 → 렌더링 → Cloud Storage 저장.
  • AI 호출 진입점은 클라이언트 generateLegalDoc 이며, RAG context 는 서버 액션 getRagContextForDocGenAction 이 prepare 한다.
  • 1.1 에서 결합 위치를 정정 — getRagContextForDocGenAction (server action, RAG prepare) + generateLegalDoc (client, AI 호출).

기존 getRagContextForDocGenAction 의 한계:

  • 단순 caseId equality query 만 (legacyDocsRef.where("caseId", "==", caseId).limit(5)) — findNearest 미사용
  • Platform RAG 는 별도 find-related-precedents-action.ts 로만 호출, generateLegalDoc 에 미주입

현재 AI 서류 생성은 "같은 사건의 사무실 기억 스니펫" 만 prompt 에 들어가고, 의미 기반 RAG 두 개 모두 자동 결합 안 됨.

배경

사용자 의도 (2026-05-08 명시)

"AI 서류 생성을 위해 법제처 데이터를 RAG 로 만들어서 공통으로 사용하고, 각자의 테넌트는 각자의 사무실 기억을 참조해서 서류를 만든다."

현재 동작 (2026-05-08 매핑)

컴포넌트현재 호출 경로
Platform RAG (publicDocuments)별도 Server Action find-related-precedents-action.ts 로만 호출. UI "판례 찾기" 버튼. AI 서류 생성 흐름과 분리
Tenant RAG 의미 검색 (legacyDocuments findNearest)별도 Server Action find-related-memories-v2-action.ts. UI "관련 기억" 패널. AI 서류 생성 흐름과 분리
getRagContextForDocGenActioncaseId == X Firestore equality query 5건. 임베딩 검색 미사용
Exemplar (변호사 finalize 본)fetch-authored-exemplars-action.tsselectAuthoredExemplarsgenerateLegalDoc 에 자동 포함 ✓
generateLegalDoc (client AI)(docType, caseInput, ragContext: string[], authoredExemplars) — ragContext 는 caseId equality 결과

결과: 두 RAG 가 동일 prompt 안에 자동 결합되지 않는다. 사용자 의도와 본질적 gap.

인프라는 준비됨

  • 두 컬렉션 모두 text-embedding-004 768-dim COSINE — 동일 모델
  • findNearest 패턴 검증됨 (각 별도 Server Action 으로)
  • citation-existence 환각 검증 (packages/business-logic/src/rag-eval/citation-existence.ts) verdict 7 종 ship
  • assist 카운터 (reserveAssistAction) weight 1 (docGeneration) 차감 ship

데이터 모델·검색·검증 부품은 모두 ship, 결합 시점만 누락.

결정 — 새 결합 흐름

변경 전 (현재)

useDocGenerate.handleGenerate

getRagContextForDocGenAction(caseId, docType)
├─ legacyDocsRef.where("caseId", "==", caseId).limit(5) ← caseId 매칭만
└─ return { snippets[], memories[] }

generateLegalDoc(docType, caseInput, ragSnippets)
└─ systemInstruction = baseSysPrompt + "사무실 기억 참고 자료" 섹션 (snippets join)

변경 후 (ADR 0041 1.1)

useDocGenerate.handleGenerate

getRagContextForDocGenAction(caseId, docType)
├─ readCase(caseId) ← recoveryType, summaryHint 추출
├─ embedQuery(query) ← Vertex AI text-embedding-004
├─ Promise.all([
│ tenantRag: legacyDocsRef(tenantId).findNearest(vector, 5) ← 의미 검색
│ platformRag: publicDocumentsRef.findNearest(vector, 5) ← 판례·법령
│ ])
├─ tenant-scope-assert 가드 (Tenant RAG 만)
└─ return {
tenantSnippets[], tenantMemories[], ← 하위 호환 유지
platformCards[], platformCitations[] ← 신규
}

generateLegalDoc(docType, caseInput, ragContext)
└─ systemInstruction =
baseSysPrompt
+ "사무실 기억" 섹션 (tenantSnippets join)
+ "판례·법령" 섹션 (platformCitations marker join) ← 신규
+ 변호사 finalize exemplar (기존)

verifyCitationExistence(생성 텍스트의 citation 추출)
├─ publicDocuments 직접 조회 verdict 7 종
└─ hallucinationRate 계산

docgenEvent (hallucinationRate, ragLayers, citationVerdicts 포함)

확장된 ragContext schema

// apps/web/types/rag-context.ts (신설)
export const ragContextSchema = z.object({
// Tenant RAG (사무실 기억) — caseId-equality 에서 의미 검색으로 업그레이드
tenantSnippets: z.array(z.string()),
tenantMemories: z.array(ragContextMemorySchema),
// Platform RAG (판례·법령) — 신규
platformCards: z.array(publicRagCardSchema),
platformCitations: z.array(publicRagCitationSchema),
});

generateLegalDoc 시그니처 확장

// 변경 전
export async function generateLegalDoc(
docType: string,
caseInput: LegalCaseInput,
ragContext?: string[], // Tenant 사무실 기억 스니펫만
authoredExemplarsSection?: string,
)

// 변경 후 (하위 호환)
export async function generateLegalDoc(
docType: string,
caseInput: LegalCaseInput,
ragContext?: string[] | {
tenantSnippets?: string[];
platformCitations?: PublicRagCitation[];
},
authoredExemplarsSection?: string,
)
// string[] 입력 시 자동으로 { tenantSnippets } 로 wrap

tenant-scope-assert 가드 재사용

getRagContextForDocGenAction 안에서 Tenant RAG findNearest 호출 직전 assertTenantScopedPath 호출 (ADR 0015 PR-5 일관 패턴, find-related-memories-v2-action.ts 동일). Platform RAG 는 tenant 격리 밖이라 가드 불필요 (ADR 0021).

citation marker 자동 삽입

prompts.ts 의 systemInstruction 에 인용 형식 명시:

판례·법령을 인용할 때 [[1]], [[2]] 형식의 marker 를 사용하세요.
사무실 기억은 (기억-N) 형식. 인용한 marker 는 본문 끝 "참고 자료" 섹션에 source 와 함께 나열.

환각 검증 inline (post-AI)

1. AI 응답 텍스트에서 statute/precedent 인용 추출 (regex `\[\[(\d+)\]\]` + 자유 텍스트)
2. platformCitations 와 매칭 → 실제 publicDocuments 에 존재하는지 verifyCitationExistence 호출
3. verdict 7 종 분류 → hallucinationRate 계산
4. 30% 초과 시 docgenEvent.warning + ops 첫 화면 anomaly slot

텔레메트리 확장

// docgenEvent schema 확장
{
...기존 필드,
ragLayers: { tenantSnippets: number, platformCitations: number, exemplar: number },
hallucinationRate: number, // 0~1
citationVerdicts: Record<verdict, number>,
}

assistWeight 정책

  • 현재: weight = 1 (docGeneration)
  • 두 RAG findNearest + verify 비용 추가 → weight 변경 ❌ (ADR 0002 정합)
  • RAG 검색 자체는 무료 reservation (Vector Search 비용 흡수)
  • 추후 호출량이 한도 압박 시 weight 2 (RAG-augmented docgen) 분리 검토 = 별도 ADR

합성 corpus 위 leave-one-out 회귀 (메모리 close_the_learning_loop 적용)

본 결정의 ship 판정은 머지 ≠ 동작. demo-firm-memory 20 cases 위에서 leave-one-out 회귀:

for case in demo cases (20):
1. case 의 exemplar/legacyDoc 를 prompt 에서 제외
2. getRagContextForDocGenAction → generateLegalDoc 호출
3. 생성된 텍스트의 citation list 추출
4. 정답 citation (ragEvalCases.expected.statutes/precedents) 와 비교
5. precision · recall · hallucinationRate 누적

회귀 통과 = 평균 precision > 0.7 AND hallucinationRate < 0.15. 통과 안 하면 ship 표시 안 함 (ADR 0030 Test Trust Crisis 정합).

합성 ground truth 인프라

scripts/eval-rag-merge.ts 신설 (Block 3) — leave-one-out 자동화. cron aggregate-rag-merge-stats.ts 매일 02:00 KST 실행 → aiQualityHistory 에 누적. 결과가 ops 첫 화면 4 축 중 "학습 회귀 통과율" 시그널로 노출.

위험·완화

위험완화
getRagContextForDocGenAction 시그니처 변경이 기존 호출처 깸반환 타입 확장 (snippets/memories 필드 그대로 + tenantSnippets/platformCitations 추가). 기존 호출처는 snippets/memories 만 사용 → 영향 0
generateLegalDoc 시그니처 변경string[] 입력 하위 호환 (tenantSnippets 으로 wrap). 점진 마이그레이션
두 RAG 병렬 findNearest latency평균 80ms 병렬, max 150ms 추가. 현재 generateLegalDoc 평균 8s 대비 무시
Tenant RAG 가 빈 사무소 (J5lz...) 에서 0 결과0 결과 OK — Platform RAG + exemplar fallback. 빈 사무소 부트스트랩
Platform RAG 의 commentary 부재 (실측)court_precedent + statute 만으로 출발. legal-news (ADR 0033) publish 가 commentary 채울 때 자동 흡수
LLM 환각 (없는 판례 인용)inline citation-existence verify, verdict law_missing/article_missing 자동 검출
기존 분리 패널 (find-related-precedents · find-related-memories) 폐기?폐기 ❌. UI manual fetch 용 유지 (변호사가 추가 검색 가능). 자동 결합은 별도 코드 경로

Non-goal

  • exemplar (변호사 finalize 본) layer 그대로 유지
  • generateDocxAction 은 본 결정의 영향 없음 (템플릿 치환만, AI 호출 안 함)
  • find-related-precedents-action.ts · find-related-memories-v2-action.ts 폐기 ❌
  • Vertex AI Search 외 검색 엔진 도입 ❌
  • multi-query expansion (ADR 0024) 은 본 ADR 와 직교 — 향후 결합 가능
  • 지역화 (다국어 RAG) — 한국어만

구현 순서 (PR 단위)

PR산출물예상 시간
0본 ADR 1.1 amendment (결합 위치 정정)0:30
1getRagContextForDocGenAction 두 RAG findNearest 업그레이드 + 반환 schema 확장 + tenant-scope-assert1:00
2generateLegalDoc 시그니처 확장 + prompts.ts citation marker1:00
3useDocGenerate 통합 + verify citation inline + 텔레메트리 확장1:00
4leave-one-out 회귀 스크립트 (Block 3)0:30

총 약 4 시간.

후속

  • 회귀 통과 후 Block 3 에서 cron 자동화 + ops 첫 화면 신뢰도 축 노출
  • ADR 0042 anomaly 가설 자동화 파이프라인은 본 결정의 hallucinationRate · ragLayers 텔레메트리 위에서 동작

변경 이력

버전날짜변경
1.02026-05-08초안 — generateDocxAction 두 RAG 자동 결합 결정 (결합 위치 오류)
1.12026-05-08결합 위치 정정 — generateDocxAction (템플릿 치환만) → getRagContextForDocGenAction + generateLegalDoc 흐름. 코드 read 후 발견된 오류. 흐름 다이어그램·schema·구현 순서 갱신