권한·인용·감사를 갖춘 프라이빗 RAG 실전
사내 문서를 외부로 보내지 않고 검색·인용하며 권한과 감사 기록을 지키는 로컬 AI를 만든다.
상태: 출간 후보 웹교정쇄 · 모델 라이선스·사내 보안 검수 대기 · 15개 장
부제: 평범한 PC로 만드는 로컬 문서실과 하이브리드 에이전트

화면의 D1은 생성 모델의 답이 아니라 결정적 검색 기준선이다. 이후 모델을 연결해도 같은 질문에서 D1을 잃거나 근거 없는 문장을 추가하면 개선이 아니다.
이 책은 “설치 성공”에서 끝나지 않는다. 문서 반입, 검색, 근거 표시, 권한, 삭제, 백업, 품질 평가까지 운영한다. 완성 프로젝트는 해솔상점 계약·매뉴얼 지식실이다. 실제 개인정보 대신 제공 fixture를 사용하므로 GPU가 없어도 학습할 수 있다. 압축을 푼 프로젝트 폴더에서 npm run qa, npm start를 실행하고 http://127.0.0.1:4173/lab/index.html의 ‘로컬 문서실’을 연다. fixtures/documents.json이 D1~D3의 정본이다.
1장 로컬이라는 말부터 분해한다
모델 파일이 PC에 있어도 문서 업로더, 텔레메트리, 임베딩 API가 외부로 나가면 완전한 로컬이 아니다. 반대로 민감 문서는 로컬에서 검색하고 공개 질문만 클라우드 모델에 보내는 하이브리드도 합리적이다. 중요한 것은 브랜드가 아니라 데이터 흐름이다.
실습 1 데이터 흐름도
문서 원본, 텍스트 추출, 청크, 임베딩, 질의, 응답, 로그를 상자로 그린다. 네트워크 경계를 넘는 화살표를 빨간색으로 표시한다. 모르는 경로는 안전하다고 가정하지 말고 미확인으로 둔다.
2장 내 컴퓨터에 맞는 크기를 고른다
모델 매개변수만 보고 하드웨어를 고르면 실패한다. 가중치 형식, 양자화, 컨텍스트, KV 캐시, 동시 요청이 메모리를 함께 쓴다. 먼저 20개의 대표 질문으로 허용 지연과 품질을 정하고 작은 모델부터 측정한다.
llama.cpp는 CPU와 여러 GPU 백엔드, 양자화 모델, 로컬 HTTP 서버를 제공한다. Ollama는 설치와 모델 실행을 단순화한다. 어느 쪽도 “모든 PC에서 빠르다”는 뜻은 아니다. 가중치의 이론적 하한은 매개변수 수 × 양자화 비트 ÷ 8이지만 파일 메타데이터, KV 캐시와 런타임 버퍼가 더 필요하다. 대략 8GB 시스템은 1~3B급, 16GB는 7~8B급, 32GB는 14B급부터 시험하되 실제 모델 파일 크기와 llama-bench 측정을 우선한다. 긴 컨텍스트와 동시 요청은 같은 모델에서도 메모리를 크게 늘린다.
실습 2 무모델 기준선
제공 실습실의 로컬 문서 탭에서 ‘환불’을 검색한다. D1 근거가 나오는지 확인한다. 이 결정론적 검색이 이후 모델 답변의 최소 기준이다. 모델이 더 유창해도 근거를 잃으면 실패다.
3장 문서 반입은 복사가 아니라 공급망이다
계약서와 매뉴얼은 출처, 소유자, 보존 기간, 민감 등급이 다르다. 파일 해시와 반입 시각을 기록하고 원본은 읽기 전용으로 둔다. OCR 결과를 원본처럼 취급하지 않는다. 표와 각주가 유실될 수 있기 때문이다.
실습 3 문서 명세서
D1 환불정책, D2 배송매뉴얼, D3 임대차계약에 소유자, 버전, 민감도, 만료일을 붙인다. 주민번호나 계좌번호가 들어간 fixture는 색인 전에 삭제하거나 마스킹한다.
4장 잘게 자르되 문맥을 자르지 않는다
고정 글자 수 청크는 제목과 예외 조항을 갈라놓는다. 제목 계층, 문단, 표의 행을 우선해 자르고 documentId, version, section, page, effectiveDate, supersedes, contentHash를 메타데이터로 보존한다. 겹침은 만능이 아니라 문맥 손실과 중복 검색의 교환이다.
실습 4 환불 예외를 보존한다
“7일 이내 환불”과 “개봉한 위생 상품 제외”가 서로 다른 청크가 되지 않게 묶는다. 질문 “개봉한 제품도 7일 이내면 되나요?”에 두 문장이 함께 검색되는지 확인한다.
5장 검색과 생성을 분리한다
검색기는 후보 근거를 찾고 생성기는 그 근거를 문장으로 만든다. 답이 틀렸을 때 둘을 분리해야 원인을 찾을 수 있다. 검색 상위 k, 키워드/벡터 혼합, 재순위화는 평가 세트로 조정한다.
const evidence = retrieve(question, documents)
.filter(item => item.score >= MIN_SCORE);
if (evidence.length === 0) return {answer:'근거 문서에서 찾지 못했습니다.', citations:[]};
return compose(question, evidence);
검색 결과 한 건은 {documentId, version, section, text, score} 구조다. MIN_SCORE는 임의로 고정하지 않고 6장의 ‘근거 없음’ 평가 세트로 조정한다. top-k 검색은 아무 질문에나 결과를 돌려줄 수 있으므로 결과 개수만으로 답변 가능 여부를 판정하지 않는다.
실습 5 모르면 모른다고 답한다
fixture에 없는 “해외 배송비”를 묻는다. 시스템이 비슷한 국내 배송 문장을 짜깁기하지 않고 근거 없음으로 끝나야 한다.
6장 답변마다 원문으로 돌아가는 문
인용 번호만 달면 충분하지 않다. 문서 ID, 버전, 쪽 또는 섹션, 사용한 문장을 함께 반환한다. 답변 문장과 근거 사이의 함의 관계를 검사한다. “가능”을 “의무”로 바꾸는 작은 과장이 실무에서는 큰 오류다.
실습 6 근거 검사표
정답 10개, 근거 없음 5개, 서로 충돌하는 버전 5개로 평가 세트를 만든다. 검색 적중, 인용 정확, 답변 충실, 정답 거부를 각각 기록한다.
7장 권한은 검색 전에 적용한다
검색 후에 민감 문장을 지우면 모델 컨텍스트와 로그에는 이미 들어갔을 수 있다. 사용자와 문서의 권한을 검색 후보 생성 전에 교차한다. 관리자 권한을 서비스 공용 키 하나로 대체하지 않는다.
실습 7 역할별 결과
점원은 D1·D2만, 대표는 D1·D2·D3을 볼 수 있게 한다. 같은 질문에 검색 후보 목록부터 달라야 한다. 화면에서 가리는 CSS는 권한 통제가 아니다.
8장 로컬 서버도 서버다
127.0.0.1 바인딩, 인증, 요청 크기, 시간 제한, 동시성, 로그 마스킹, 모델 라이선스를 확인한다. llama.cpp는 현재 릴리스의 llama-server --help에서 지원 플래그를 확인한 뒤 --host 127.0.0.1 --port 8080처럼 로컬 주소를 명시한다. Ollama는 OLLAMA_HOST=127.0.0.1:11434로 범위를 고정한다. 다른 기기에서 http://내-PC-IP:포트 접속이 실패해야 한다. 인증 지원과 플래그는 릴리스마다 달라질 수 있으므로 인쇄된 명령보다 설치 버전의 도움말을 우선한다. 모델 파일도 공급망 산출물이므로 출처와 해시를 기록한다.
실습 8 위협 모델
도난 노트북, 악성 문서, 브라우저 확장, 공유 계정, 백업 디스크를 공격자로 놓는다. 예방, 탐지, 복구 통제를 하나씩 적는다.
9장 하이브리드 라우팅
공개 번역이나 일반 문장 다듬기는 클라우드가 유리할 수 있다. 계약 조항 검색은 로컬에 남긴다. 라우터는 질문의 의미를 추측하기보다 문서 등급과 작업 유형에 따라 결정한다. 외부 전송 전에는 실제 전송될 텍스트를 사람이 볼 수 있어야 한다.
실습 9 전송 영수증
원문 대신 [CUSTOMER_NAME]으로 마스킹한 문장, 목적, 공급자, 보존 정책, 승인자를 기록한다. 승인 이후 텍스트가 바뀌면 다시 승인한다.
10장 캡스톤 — 해솔상점 지식실
문서 세 건을 명세하고, 구조 기반으로 나누고, 역할별 검색을 적용한다. 20문항 평가에서 근거 없는 답은 거부한다. 마지막으로 신규 계약서 버전을 넣는다. 같은 documentId 계열에서 새 청크의 supersedes가 기존 documentId@version을 가리키면 이전 답변의 인용을 stale로 표시한다.
완료 기준은 응답 속도만이 아니다. 네트워크를 끊어도 핵심 질의가 작동하고, 모든 답이 허용된 문서로 돌아가며, 문서 삭제 요청 후 원본·청크·임베딩·백업의 처리 상태가 기록돼야 한다.
실습 10 복원 훈련
색인 디렉터리를 새 위치로 복원하고 해시와 평가 결과를 비교한다. 모델 버전이 바뀌어 결과가 달라지면 조용히 덮지 말고 새 기준선으로 승인한다.
11장 문서 파서와 OCR의 실패를 눈으로 검수한다
PDF에서 글자가 추출됐다는 사실은 내용이 보존됐다는 뜻이 아니다. 두 단 표의 읽기 순서가 섞이거나, 각주가 본문에 붙거나, 스캔 이미지의 1과 I가 바뀔 수 있다. 파서 버전, 페이지 수, 추출 글자 수, 표 수, OCR 사용 여부, 경고를 반입 기록에 남긴다.
계약서와 가격표는 표본 페이지를 원본과 나란히 본다. 제목 계층, 표 행·열, 예외 문구, 날짜, 금액, 부정 표현을 확인한다. OCR 신뢰도가 낮은 페이지는 자동 색인하지 않고 검수 대기열로 보낸다.
{"documentId":"D3","parser":"fixture-parser-1","pages":12,"ocr":true,"warnings":["page-7-low-confidence","table-2-merged-cells"],"status":"review"}
실습 11 파서 검수표
가상 계약서 3쪽을 선택해 원본, 추출문, 청크를 세 열로 비교한다. 날짜·금액·표·아니다 같은 부정어가 달라지면 중대 오류로 표시한다. 글자 수가 비슷하다는 이유로 통과시키지 않는다.
12장 검색 품질을 질문 세트로 측정한다
검색 평가는 “그럴듯한 문서가 위에 왔다”가 아니라 정답 근거가 몇 위에 있는지로 본다. 답이 있는 질문, 여러 문서를 합쳐야 하는 질문, 답이 없는 질문, 권한 밖 질문을 나눈다. Recall@k와 MRR 같은 검색 지표는 유용하지만 최종 답의 근거 충실성과 별개다.
| 질문 | 기대 근거 | 허용 역할 | 기대 행동 |
|---|---|---|---|
| 환불 기한은? | D1 환불 조항 | support | 기한+영수증 조건 인용 |
| 개봉한 위생 상품은? | D1 예외 | support | 예외까지 함께 제시 |
| 임대료는? | D3 금액 | legal | 인용 |
| 대표의 집 주소는? | 없음 | 모든 역할 | 찾지 못함·수집 금지 |
키워드 검색, 벡터 검색, 혼합 검색의 결과를 같은 질문 세트로 비교한다. 한 번에 여러 설정을 바꾸지 않는다. 청크, 임베딩, top-k, 재순위화 중 하나만 바꾸고 결과와 비용을 기록한다.
실습 12 20문항 기준선
정답 있음 10, 다중 근거 4, 답 없음 3, 권한 밖 3문항을 만든다. D1~D3 fixture만으로 먼저 실행한다. 답 없음이 임의의 상위 문서를 받아 답을 꾸미지 않는지 확인한다.
13장 답변 충실성과 인용을 기계적으로 검사한다
문장이 근거와 같은 주제를 말한다고 충실한 것은 아니다. 숫자, 날짜, 조건, 예외가 근거에 실제로 있어야 한다. 답변을 주장 단위로 나누고 각 주장에 문서 ID, 버전, 섹션을 연결한다. 연결되지 않은 주장은 삭제하거나 추론으로 명시한다.
{"answer":"구매 후 7일 이내이며 영수증이 필요합니다.","claims":[{"text":"구매 후 7일 이내","evidence":["D1:v3:refund"]},{"text":"영수증 필요","evidence":["D1:v3:refund"]}],"unverified":[]}
링크만 붙이는 방식은 부족하다. 독자가 클릭했을 때 해당 문장과 버전을 찾을 수 있어야 한다. 문서가 개정되면 과거 답변의 인용은 당시 버전을 보존하거나 더 이상 유효하지 않다고 표시한다.
실습 13 주장-근거 대조
정상 답변 하나에 근거 없는 “무료 배송” 문장을 섞는다. 검사표가 해당 주장을 unverified로 잡아야 한다. 그 문장을 삭제한 뒤 인용 링크가 D1의 정확한 섹션으로 가는지 본다.
14장 성능·비용·자원을 함께 운영한다
로컬 모델은 호출료가 없더라도 무료가 아니다. 메모리, 전력, 저장 공간, 운영 시간, 장애 대응 비용이 든다. 첫 토큰 지연, 전체 응답 시간, 초당 토큰, 검색 시간, 동시 요청 수, 메모리 최고점을 같은 조건에서 측정한다.
긴 컨텍스트에 모든 문서를 넣는 방식은 느리고 권한 경계도 흐린다. 검색 결과의 필요한 구간만 넣고 최대 컨텍스트와 답변 길이를 제한한다. 요청이 몰리면 무한 대기시키지 말고 큐 길이, 제한 시간, 취소, 텍스트 검색 대안을 제공한다.
장치: 16GB 노트북, 전원 연결
모델/양자화: 배포 시 기록
질문 세트: eval-v1
동시 요청: 1 / 2 / 4
측정: 검색 p95, 첫 토큰 p95, 전체 p95, 메모리 최고점
중단: 메모리 압박 또는 30초 초과
실습 14 무모델과 모델 비교
같은 20문항을 결정적 검색, 작은 로컬 모델, 선택적 클라우드 경로로 실행한다. 유창함만 비교하지 말고 근거 정답률, 거절 정확성, 지연, 전송된 데이터 범위를 표로 만든다.
15장 백업·삭제·모델 교체를 릴리스로 관리한다
백업 대상은 모델 파일만이 아니다. 원본 문서, 반입 명세, 청크 설정, 인덱스 버전, 권한 정책, 평가 세트, 감사 로그의 보존 범위를 정한다. 비밀과 개인정보가 든 원본 백업은 암호화하고 복구 권한을 분리한다.
삭제는 원본 파일 하나를 지우는 것으로 끝나지 않는다. 추출문, 청크, 벡터 인덱스, 캐시, 답변 기록, 백업 만료를 추적한다. 즉시 지울 수 없는 백업은 접근을 막고 만료 일정을 기록한다. 모델·임베딩·파서가 바뀌면 새 릴리스로 취급해 전체 색인과 평가를 다시 만든다.
릴리스: knowledge-room-2026.08.1
원본 스냅샷: docs-manifest sha256
파서/청크: parser-2 / heading-v3
임베딩: model-id + digest
권한 정책: policy-v5
평가: eval-v3, 20/20 실행
되돌림: 이전 읽기 전용 인덱스
실습 15 복구와 삭제 리허설
D2를 삭제 요청 대상으로 표시하고 검색·캐시·인덱스에서 사라지는지 확인한다. 그다음 깨끗한 폴더에서 D1과 D3만 복원해 권한과 평가를 실행한다. 삭제된 D2가 답변이나 로그에 다시 나타나면 실패다.
공식 자료와 최신성 메모
- llama.cpp 공식 README — 설치·서버·벤치마크 기능
- llama.cpp 서버 문서 — 로컬 바인딩, API, 도구 기능
- Ollama 공식 블로그 — 2026년 로컬·하이브리드 실행 변화
- OpenAI 모델 공식 문서 — 공개 가중치 모델을 포함한 현재 모델 자료의 확인 출발점
명령과 모델명은 빨리 변한다. 인쇄 전 공식 릴리스와 라이선스를 다시 확인하고, 본문은 데이터 흐름·평가·권한이라는 변하지 않는 축을 정본으로 삼는다.
실습 워크북: 해솔상점 지식실 15개 미션
모든 미션은 실제 회사 문서 대신 D1~D3 fixture로 수행한다. 실제 문서를 넣기 전에는 데이터 소유자, 보존, 권한, 외부 전송 경로를 승인받아야 한다.
- 데이터 흐름: 원본부터 로그까지 상자를 그리고 네트워크 경계를 표시한다.
- 무모델 기준선:
환불검색에서 D1이 나오는지 고정한다. - 문서 명세: 소유자·버전·민감도·해시·삭제일을 적는다.
- 파서 표본: 날짜·금액·표·부정어가 원본과 같은지 비교한다.
- 청크 계약: 제목·예외·표 행을 보존하고 버전 메타데이터를 붙인다.
- 검색 분리: 검색 결과와 생성 답변을 각각 저장한다.
- 답 없음: 근거가 없는 질문에 임의 답변을 만들지 않는다.
- 인용 문: 모든 공개 주장에 문서 ID·버전·섹션을 연결한다.
- 권한 전 필터: legal 전용 D3가 support 검색 후보에도 들어가지 않게 한다.
- 하이브리드 영수증: 클라우드로 보낼 필드와 보내지 않을 원문을 기록한다.
- 질문 세트: 정답·다중 근거·답 없음·권한 밖 20문항을 만든다.
- 충실성 주입: 근거 없는 한 문장을 넣어 검사가 실패하는지 본다.
- 성능 측정: 장치·모델·동시성·p95·메모리를 기록한다.
- 삭제: D2의 원본·파생 데이터·캐시·백업 만료를 추적한다.
- 복구: 깨끗한 환경에서 인덱스를 만들고 평가·권한 테스트를 재실행한다.
완주 판정표
| 항목 | 0점 | 1점 | 2점 |
|---|---|---|---|
| 데이터 흐름 | 로컬이라 가정 | 일부 경로 | 원본·모델·로그·외부 경계 확인 |
| 문서 공급망 | 파일 복사 | 해시만 있음 | 소유자·버전·파서·삭제 연결 |
| 근거 | 답변만 있음 | 문서 링크 | 주장별 버전·섹션·예외 연결 |
| 권한 | 답변 후 필터 | 문서 필터 | 검색 전 필터와 네거티브 테스트 |
| 평가 | 예시 질문 몇 개 | 정답 질문 | 답 없음·권한·홀드아웃 포함 |
| 운영 | 서버 실행 | 백업 있음 | 삭제·복구·모델 교체 리허설 |
총점 10점 이상이고 근거와 권한이 각각 2점이어야 출간 실습을 완주한 것으로 본다. 실제 사내 배포는 개인정보·보안·노무·계약 요건을 조직 책임자와 별도로 검토한다.
현장 트러블슈팅: 문서에서 답변까지 층별로 고친다
RAG 장애를 모두 모델 문제라고 부르면 원인을 찾기 어렵다. 반입 → 추출 → 청크 → 색인 → 권한 필터 → 검색 → 생성 → 인용 → 삭제 순서로 첫 번째 잘못된 층을 찾는다. 한 번에 모델과 임계값과 프롬프트를 모두 바꾸지 않는다.
1 검색 결과가 하나도 없다
문서가 반입 명세에 있고 현재 인덱스 버전에 포함됐는지 확인한다. 질의 정규화, 언어, 키워드 토큰, 임계값을 차례로 본다. 먼저 fixture의 정확한 단어 환불로 결정적 검색을 실행한다. 이것도 비면 모델이 아니라 반입·색인 문제다.
npm run qa
npm start
현재 폴더와 화면의 fixture 버전이 다르면 결과를 비교하지 않는다.
2 관련 없는 문서가 항상 상위에 나온다
top-k는 어떤 질문에도 결과를 반환할 수 있다. 점수 분포와 답 없음 질문을 함께 보고 임계값을 조정한다. 문서 제목이 모든 청크에 과도하게 반복되거나 큰 청크가 공통 단어를 독점하는지 확인한다. 검색 실패를 생성 프롬프트로 덮지 않는다.
3 환불 기한은 나오지만 예외가 빠진다
기한과 예외가 다른 청크로 갈라졌는지 본다. 예외 청크의 제목·문서 ID·버전이 보존됐는지 확인하고, 다중 근거 질문의 기대 ID에 두 청크를 등록한다. 무조건 청크 겹침을 크게 하기보다 의미 단위로 다시 자른다.
4 OCR 문서의 금액이 달라졌다
해당 페이지를 자동 답변 대상에서 빼고 검수 상태로 바꾼다. 원본 이미지와 추출문을 나란히 보고 숫자·통화·소수점·부정어를 확인한다. 사람이 수정한 값에는 수정자와 원본 좌표를 남긴다. OCR 결과를 조용히 원본으로 덮어쓰지 않는다.
5 점원에게 법무 문서가 보인다
답변 뒤 마스킹으로 끝내지 않는다. 검색 후보를 만들기 전에 사용자 신원과 문서 ACL을 적용했는지 확인한다. 캐시 키에 사용자 역할·정책 버전이 포함됐는지 본다. 이미 노출됐다면 감사 로그로 범위를 확인하고 관련 캐시를 폐기한다.
6 이전 계약서가 현재 답으로 나온다
같은 documentId 계열의 활성 버전과 효력일, supersedes 연결을 확인한다. 과거 버전은 삭제하지 않더라도 현재 검색 후보에서 제외하고 감사·과거 답변 확인 용도로 읽기 전용 보존한다. 질문이 특정 과거 날짜를 요구하면 그 시점 버전을 명시해 반환한다.
7 근거가 없는데 모델이 답한다
검색 결과가 비었을 때 생성기를 호출하지 않는 코드 경계를 확인한다. 낮은 점수의 관련 없는 청크가 들어갔다면 답 없음 세트로 임계값을 다시 본다. 거절 문구가 불친절하더라도 근거 없는 확정 답보다 낫다. 사용자가 문의할 담당자나 필요한 문서 종류를 안내할 수 있다.
8 인용 링크가 문서 첫 페이지로만 간다
문서 ID만 저장하지 말고 버전, 섹션, 페이지 또는 문자 범위를 보존한다. PDF 뷰어가 직접 위치 링크를 지원하지 않으면 해당 구간을 강조한 읽기 화면을 만든다. 권한 없는 사용자가 링크를 통해 원문 전체에 접근하지 않게 같은 ACL을 적용한다.
9 응답이 느리고 메모리가 부족하다
검색, 재순위화, 첫 토큰, 전체 생성 시간을 나눈다. 컨텍스트에 중복 청크와 전체 문서를 넣는지 확인한다. 동시 요청을 1로 줄여 기준선을 측정하고 작은 모델·짧은 컨텍스트·짧은 답변부터 비교한다. 운영체제의 메모리 압박이 생기면 더 큰 모델을 억지로 유지하지 않는다.
10 로컬 서버가 다른 기기에서도 열린다
바인딩 주소를 확인하고 필요 없으면 127.0.0.1로 제한한다. 단순히 사내망이라 안전하다고 보지 않는다. 공유가 필요하면 인증, TLS, 사용자별 권한, 요청 제한, 감사가 있는 게이트웨이를 둔다. 포트 포워딩과 방화벽 예외도 자산 목록에 기록한다.
11 하이브리드 경로로 원문이 전송됐다
라우팅을 중단하고 공급자 로그와 전송 영수증으로 범위를 확인한다. API 키 회전만으로 문서 유출이 해결되지는 않는다. 데이터 소유자와 보안 책임자에게 사실과 미확인 사항을 알리고, 전송 전에 실행되는 마스킹·승인 코드와 네거티브 테스트를 추가한다.
12 삭제한 문서가 검색 결과에 다시 나온다
원본, 추출문, 청크, 벡터 인덱스, 키워드 인덱스, 응답 캐시, 대화 기록을 각각 확인한다. 백업에서 즉시 물리 삭제할 수 없다면 접근을 차단하고 만료 시각을 기록한다. 재색인 작업이 오래된 명세에서 문서를 다시 반입하지 않는지도 본다.
13 복구 뒤 검색 결과가 달라졌다
모델·임베딩·파서·청크·라이브러리 버전과 원본 해시를 비교한다. 다른 임베딩으로 만든 인덱스를 섞지 않는다. 동일 릴리스라면 같은 질문 세트와 권한 결과를 재현해야 한다. 새 버전이면 차이를 승인하고 새 기준선으로 기록한다.
14 모델은 한국어를 잘하지만 인용이 약하다
언어 유창성과 근거 충실성을 분리해 평가한다. 답변의 각 주장에 근거를 강제로 연결하고, 연결되지 않은 문장은 삭제한다. 큰 모델로 바꾸기 전에 검색 결과가 정답인지와 예외가 함께 들어왔는지 확인한다.
15 정상 질문까지 너무 자주 거절한다
거절률을 낮추려고 임계값을 무조건 내리지 않는다. 정상 질문의 표현 다양성, 동의어, 오타, 문서 제목을 평가 세트에 추가한다. 키워드와 벡터 혼합, 재질문, 검색 결과 미리보기를 비교한다. 답 없음 오답률과 정상 거절률을 함께 본다.
출간 전 현장 검수
실제 회사 문서 없이 전체 흐름을 완주할 수 있어야 한다. 선택 모델 설치가 실패해도 무모델 코스는 동작해야 하며, 명령·모델 이름·라이선스는 출간 시점 공식 문서로 다시 확인한다. 책의 하드웨어 표는 보장이 아니라 시작점으로 표시하고 장치·컨텍스트·동시성에 따라 달라짐을 반복해서 알린다.
독자가 남겨야 할 산출물은 데이터 흐름도, 문서 명세, 파서 검수표, 청크 계약, 질문 세트, 권한 행렬, 주장-근거 장부, 전송 영수증, 성능표, 삭제·복구 기록이다. 이 열 가지가 있으면 특정 모델이 바뀌어도 시스템을 다시 설명하고 검증할 수 있다.
평가 결과 읽기: 같은 오답도 고칠 층이 다르다
질문 “개봉한 위생 상품도 7일 안이면 환불되나요?”에 시스템이 “7일 이내 환불됩니다”라고 답했다고 하자. 먼저 검색 결과를 본다. D1의 기한 청크만 있고 예외 청크가 없다면 검색·청크 문제다. 두 청크가 모두 있는데 생성 답변이 예외를 빼면 생성·충실성 문제다. 예외 청크가 legal 역할에만 허용되도록 잘못 표시됐다면 메타데이터·권한 문제다.
질문 ID: E-07
기대 근거: D1:v3:refund-window + D1:v3:hygiene-exception
검색 실제: refund-window만 반환
답변 실제: “7일 이내 환불됩니다.”
판정: RETRIEVAL_FAIL
수정: 예외를 같은 의미 단위로 보존
회귀: E-07과 답 없음 세트를 함께 재실행
반대로 검색 결과는 맞지만 답변에 “무료 반품 배송”이 추가됐다면 GENERATION_UNSUPPORTED다. 프롬프트만 고친 뒤 E-07을 통과해도 새 질문에서 반복될 수 있다. 주장마다 근거 연결을 요구하고 연결 실패 문장을 제거하는 구조적 검사를 추가한다.
권한 누출과 답 없음 실패를 따로 본다
점원 질문에서 D3 임대차계약이 검색되면 답변이 생성되지 않았더라도 보안 실패다. 민감 문서가 모델 컨텍스트와 로그에 들어갔기 때문이다. 기대 결과는 빈 답변이 아니라 D3이 검색 후보에 한 번도 들어오지 않는 것이다.
답 없음 질문 “해외 배송 보험료는?”에 D2가 상위 결과로 나왔다고 무조건 오답은 아니다. 검색기는 관련 후보를 반환할 수 있다. 생성 단계가 해당 문서에 보험료가 없음을 확인하고 정직하게 거절해야 한다. 검색 점수가 낮고 내용도 무관하다면 검색 임계값을 조정한다. 두 층의 책임을 섞지 않는다.
릴리스 비교표
| 지표 | v1 | v2 | 해석 |
|---|---|---|---|
| 정답 근거 Recall@3 | 16/20 | 19/20 | 검색 개선 |
| 근거 없는 주장 | 2 | 0 | 충실성 개선 |
| 답 없음 오답 | 0 | 2 | 보호 지표 악화, 출시 중단 |
| 권한 누출 | 0 | 0 | 필수 유지 |
| 전체 p95 | 4.2초 | 7.8초 | 사용성 비용 증가 |
v2의 정답 근거율이 좋아도 답 없음 오답이 생겼다면 자동 출시하지 않는다. 어떤 지표가 보호 지표인지 미리 정해야 평균 개선에 가려진 위험을 볼 수 있다.
독자가 직접 만드는 한 페이지 품질 보고서
보고서에는 릴리스 ID, 문서 스냅샷, 질문 세트 버전, 사용자 역할, 장치, 검색 설정, 모델, 결과, 실패 목록, 남은 한계를 쓴다. “정확도 95%”만 쓰지 않는다. 표본이 20개라면 한 건이 5%포인트를 바꾼다는 점과 질문 범위를 명시한다.
출시 판단: 보류
이유: 답 없음 질문 2건에서 근거 없는 확정
사용 가능한 범위: support 역할의 D1·D2 검색 미리보기
비활성 범위: 자동 답변과 하이브리드 전송
다음 행동: 거절 게이트 수정, 새 홀드아웃 5건 추가
이 문서를 다른 사람이 읽고 같은 판단을 내릴 수 있어야 한다. 모델 이름보다 어떤 문서와 질문과 권한에서 검증했는지가 더 중요하다.
판정과 근거를 같은 릴리스 기록에 보존한다.
부록 A 초보자를 위한 90분 무모델 코스
처음 15분에는 npm run qa로 fixture와 검색 함수를 확인한다. 다음 15분에는 npm start 후 로컬 문서실에서 ‘환불’, ‘배송’, ‘해외 배송’을 검색한다. 앞의 두 질문은 근거가 나오고 마지막 질문은 비어야 한다. 그다음 20분에는 fixtures/documents.json을 읽되 원본을 바꾸지 않고 메모장에 각 문서의 소유자와 권한을 적는다.
다음 20분에는 D1의 “개봉한 위생 상품 제외”를 포함한 질문 다섯 개를 만든다. 검색된 문장만으로 답할 수 있는지 표시한다. 마지막 20분에는 점원과 대표 역할을 나누고 D3이 점원 후보 목록에 들어오지 않아야 하는 이유를 설명한다. 이 코스는 생성 모델 없이 검색·근거·권한의 뼈대를 이해하게 한다.
기대 출력과 실패 읽기
‘환불’ 검색은 id: D1, score: 1을 포함한다. ‘해외 배송’은 빈 배열이어야 한다. 현재 fixture의 점수는 교육용 0/1이므로 실제 벡터 점수와 같지 않다. 실제 모델 단계에서 임계값을 그대로 1로 복사하지 않는다.
오류는 층별로 읽는다. 파일을 못 찾으면 반입, 검색 결과가 비면 색인, 잘못된 문서가 나오면 검색·권한, 근거는 맞는데 답이 틀리면 생성 문제다. 한꺼번에 프롬프트를 바꾸지 않는다.
부록 B 실제 모델을 붙이는 선택 코스
먼저 설치 도구의 공식 문서에서 현재 버전과 라이선스를 확인한다. 모델 카드에서 언어, 컨텍스트, 사용 제한, 양자화 출처를 기록한다. 모델 파일 해시와 실행 명령을 MODEL-RECEIPT.md에 남긴다. 새 모델은 기존 20문항 평가를 통과하기 전 기본 모델이 되지 않는다.
로컬 서버는 개인 PC의 루프백 주소에만 연다. 방화벽 경고를 무시하지 않는다. 다른 기기에서 접근할 이유가 생기면 인증과 TLS가 있는 별도 게이트웨이를 설계하고, 단순히 0.0.0.0으로 바꾸지 않는다. 회사 문서를 실험할 때는 조직의 보안 정책과 승인을 먼저 확인한다.
속도 표에는 첫 토큰 시간, 초당 토큰, 최대 메모리, 질문 길이, 모델 버전을 함께 쓴다. “빠르다”는 형용사 대신 사용자가 기다릴 수 있는 한도를 쓴다. 품질 표에는 검색 적중, 인용 정확, 근거 없는 질문의 거부, 권한 누출을 분리한다.
부록 C 삭제와 복구 런북
문서 삭제 요청이 오면 원본만 지우지 않는다. 추출 텍스트, 청크, 임베딩, 캐시, 대화 로그, 백업의 위치를 찾고 각각 삭제 또는 보존 예외를 기록한다. 법적 보존 의무가 있으면 임의 삭제 대신 접근 차단과 보존 근거를 남긴다.
복구 훈련은 깨끗한 폴더에서 시작한다. 문서 명세서와 해시를 읽고 원본을 복원한 뒤 같은 청크 수와 권한 목록이 나오는지 확인한다. 임베딩 모델이 달라졌다면 과거 인덱스를 섞지 않고 새 버전으로 전량 생성한다. 복구 성공은 서버가 켜진 순간이 아니라 평가 세트와 권한 테스트가 다시 통과한 순간이다.
공식 참고 자료
기술·가격·정책은 바뀔 수 있으므로 실제 적용 전에 아래 원문을 다시 확인하세요.
