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 서류 생성 흐름과 분리 |
| getRagContextForDocGenAction | caseId == X Firestore equality query 5건. 임베딩 검색 미사용 |
| Exemplar (변호사 finalize 본) | fetch-authored-exemplars-action.ts → selectAuthoredExemplars — generateLegalDoc 에 자동 포함 ✓ |
| generateLegalDoc (client AI) | (docType, caseInput, ragContext: string[], authoredExemplars) — ragContext 는 caseId equality 결과 |
결과: 두 RAG 가 동일 prompt 안에 자동 결합되지 않는다. 사용자 의도와 본질적 gap.
인프라는 준비됨
- 두 컬렉션 모두
text-embedding-004768-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 |
| 1 | getRagContextForDocGenAction 두 RAG findNearest 업그레이드 + 반환 schema 확장 + tenant-scope-assert | 1:00 |
| 2 | generateLegalDoc 시그니처 확장 + prompts.ts citation marker | 1:00 |
| 3 | useDocGenerate 통합 + verify citation inline + 텔레메트리 확장 | 1:00 |
| 4 | leave-one-out 회귀 스크립트 (Block 3) | 0:30 |
총 약 4 시간.
후속
- 회귀 통과 후 Block 3 에서 cron 자동화 + ops 첫 화면 신뢰도 축 노출
- ADR 0042 anomaly 가설 자동화 파이프라인은 본 결정의 hallucinationRate · ragLayers 텔레메트리 위에서 동작
변경 이력
| 버전 | 날짜 | 변경 |
|---|---|---|
| 1.0 | 2026-05-08 | 초안 — generateDocxAction 두 RAG 자동 결합 결정 (결합 위치 오류) |
| 1.1 | 2026-05-08 | 결합 위치 정정 — generateDocxAction (템플릿 치환만) → getRagContextForDocGenAction + generateLegalDoc 흐름. 코드 read 후 발견된 오류. 흐름 다이어그램·schema·구현 순서 갱신 |