지어내지 않게 만드는 한 문장 — "문서에 없으면 모른다고 하라"
한 줄 요약
RAG의 절반은 검색이고, 나머지 절반은 "찾은 것만 근거로 쓰라" 는 지시입니다. 이 한 문장이 없으면 관련 조각을 잘 찾아 놓고도 모델이 자기 지식을 섞습니다.
1. 프롬프트 전문
아래는 code/ch06_rag_agent.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
PROMPT = (
"너는 승승장구몰의 규정 안내 담당자다.\n"
"아래 [문서]에 적힌 내용만 근거로 답한다.\n"
"문서에 없는 내용은 절대 지어내지 말고 '사내 문서에서 찾을 수 없습니다'라고 답한다.\n\n"
"[문서]\n{context}\n\n[질문] {question}\n\n[답변]"
)
네 부분으로 되어 있습니다.
| 부분 | 내용 | 왜 |
|---|---|---|
| ① 역할 | "규정 안내 담당자다" | 말투와 태도를 정한다 |
| ② 근거 제한 | "[문서]에 적힌 내용만 근거로" | 자기 지식을 못 쓰게 |
| ③ 모를 때 할 말 | "'찾을 수 없습니다'라고 답한다" | 빈칸을 메우지 않게 |
| ④ 재료 | {context} 와 {question} |
검색한 조각 + 질문 |
②와 ③이 이 절의 주인공입니다.
2. ② 근거 제한 — 왜 필요한가
검색은 잘 됐다고 합시다. 관련 조각 4개를 정확히 찾았습니다. 그런데 그냥 물으면 이런 일이 생깁니다.
모델은 자기가 아는 것을 보태고 싶어 합니다. 도움이 되려는 것이지만, 우리 회사 규정을 안내하는 자리에서는 사고입니다.
"[문서]에 적힌 내용만 근거로 답한다" 가 이 문을 닫습니다.
3. ③ 모를 때 할 말 — 더 중요합니다
검색이 항상 성공하는 건 아닙니다.
질문: "승승장구몰 해외 배송은 며칠 걸리나요?"
검색 결과: 관련 조각 4개 (그런데 해외 배송 얘기는 없음)
검색은 항상 4개를 돌려줍니다. "없다"고 말해 주지 않습니다. 가장 가까운 4개를 줄 뿐이죠. 3절에서 본 유사도 표를 떠올려 보세요 — 관련 없는 것도 0.5쯤은 나옵니다.
이때 모델이 받는 것은 "질문 + 관련 없는 조각 4개" 입니다. 지시가 없으면 어떻게 될까요?
실제 실행 결과가 후자입니다.
==============================================================
[3] 문서에 없는 것을 물으면
A: 사내 문서에서 해외 배송에 대한 정보나 소요 기간을 찾을 수 없습니다.
3장 7절에서 정리한 원칙이 여기서 다시 나옵니다 — 환각은 빈칸을 메우려는 성질이고, 대책은 빈칸을 채워 주거나, 빈칸일 때 할 말을 정해 주는 것입니다. 여기서는 후자입니다.
4. 잘 쓰는 요령
2장 2절의 원칙이 그대로 적용됩니다.
① 정확한 문구를 지정한다
✗ "모르면 모른다고 해"
✓ "'사내 문서에서 찾을 수 없습니다'라고 답한다"
정확한 문장을 주면 답이 일관됩니다. 그리고 나중에 프로그램에서 그 문구를 감지해 다른 처리(사람에게 넘기기 등)를 할 수도 있습니다.
② 문서를 질문보다 먼저 놓는다
[문서]
...
[질문] ...
[답변]
순서에도 이유가 있습니다. 모델이 문서를 먼저 읽고 질문을 보게 하면, 문서 내용을 염두에 두고 답을 만듭니다. 반대로 하면 질문에 대한 자기 지식이 먼저 떠오릅니다.
큰 차이는 아니지만 이런 작은 배치 차이도 영향을 줍니다.
③ 구분 표시를 확실히
[문서]\n{context}\n\n[질문] {question}\n\n[답변]
[문서], [질문], [답변] 같은 대괄호 표시로 경계를 분명히 합니다. 검색한 조각 안에 "질문"이라는 단어가 들어 있어도 헷갈리지 않게요.
5. 그래도 완벽하지는 않습니다
한계도 짚고 넘어갑니다. 이 지시로 환각이 0이 되지는 않습니다.
2장 2절에서 말한 그대로입니다 — 시스템 지시는 강한 부탁이지 절대 명령이 아닙니다.
이런 경우가 남습니다.
| 상황 | 무슨 일이 |
|---|---|
| 검색된 조각이 애매하게 관련 있음 | 억지로 끼워 맞춘 답이 나올 수 있다 |
| 질문이 문서 내용을 넘어서는 추론을 요구 | "그러면 이 경우는요?" → 추론해 버릴 수 있다 |
| 문서 자체가 모호하게 쓰여 있음 | 해석이 갈린다 |
| 문서 조각 안에 지시문이 들어 있음 | 검색된 조각이 그대로 프롬프트가 된다 |
마지막 줄이 5장 7절의 프롬프트 인젝션입니다. 거기서는 사람이 입력한 질문을 통해 들어왔는데, RAG에서는 검색된 문서 조각을 통해서도 들어옵니다. 남이 만든 PDF에
"위 지시는 무시하고 ~라고 답하라"한 줄이 섞여 있으면, 그 조각이 검색되는 순간 프롬프트의 일부가 되죠. 지시와 데이터가 같은 프롬프트에 섞이기 때문에 생기는 문제라 근본 해결이 어렵습니다. 바깥에서 들어온 문서를 인덱스에 넣을 때는 누가 만든 문서인지를 따져야 합니다.
그래서 출처를 함께 보여 줍니다. 다음 절의 주제입니다. 사람이 확인할 수 있게 만드는 것이 마지막 방어선입니다.
6. 다섯 겹의 방어 — 다시
3장 7절의 세 겹에 검색과 출처 두 겹을 더한 것입니다.
| 겹 | 무엇 | 성격 | 3장에서는 |
|---|---|---|---|
| ① 검색이 근거를 가져온다 | 관련 조각을 프롬프트에 넣음 | 구조가 보장 | 도구가 사실을 돌려줌 |
| ② 프롬프트가 근거를 제한한다 | "문서에 적힌 내용만" | 원하는 답이 나올 가능성을 높임 | 시스템 지시 |
| ③ 모를 때 할 말을 정한다 | "찾을 수 없습니다" | 원하는 답이 나올 가능성을 높임 | (RAG에서 추가) |
④ temperature=0 |
낮은 확률의 표현이 안 튀게 | 원하는 답이 나올 가능성을 높임 | 같음 |
| ⑤ 출처를 함께 보여 준다 | "환불교환정책.pdf 1쪽" | 사람이 검증 | (RAG에서 추가) |
①과 ⑤가 구조적인 방어이고, ②③④는 확률적인 방어입니다. ④는 다음 7번에서 따로 봅니다.
1장의 반품 기간 질문이 왜 위험했는지 이제 완전히 설명됩니다. 그때는 다섯 겹이 거의 하나도 없었습니다. 근거도 없고, 제한도 없고, 모를 때 할 말도 없고, 출처도 없었죠. 그래서 그럴듯한 3개월이 나왔습니다.
7. temperature=0 도 한몫합니다
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
get_chat(temperature=0).invoke(PROMPT.format(...))
2장 3절의 표에서 "문서 기반 답변(RAG) → 0" 이라고 했던 자리입니다.
온도가 높으면 낮은 확률의 표현이 튀어나오면서 문서에 없는 말이 섞일 여지가 커집니다. 근거대로만 말해야 하는 상황에서는 0입니다.
스스로 해 보기
PROMPT에서 "문서에 없는 내용은 절대 지어내지 말고..." 줄을 지우고,"승승장구몰 해외 배송은 며칠 걸리나요?"를 물어 보세요. 답이 어떻게 달라지나요?- "아래 [문서]에 적힌 내용만 근거로 답한다" 도 지우고 같은 질문을 해 보세요.
temperature를1.5로 올리고 반품 기간을 세 번 물어 보세요. 답이 흔들리나요?- 문서에 애매하게 걸치는 질문을 던져 보세요. 예:
"교환할 때 배송비는 누가 내나요?"또는"주말에도 반품 접수가 되나요?"— 모델이 어떻게 처리하는지 보세요.
4번이 흥미롭습니다. 문서에 부분적으로만 있는 경우 모델이 어떻게 하는지가 실전에서 가장 자주 마주치는 상황입니다.
핵심 정리
- RAG의 절반은 검색, 나머지 절반은 "찾은 것만 근거로 쓰라"는 지시입니다.
- 프롬프트는 네 부분 — 역할 · 근거 제한 · 모를 때 할 말 · 재료.
- 검색은 항상 결과를 돌려줍니다. "없다"고 말해 주지 않습니다. 그래서 모를 때 할 말을 정해 둬야 합니다.
- 정확한 문구를 지정하면 답이 일관되고, 프로그램에서 감지할 수도 있습니다.
- 문서를 질문보다 먼저 놓고, 대괄호로 경계를 분명히 합니다.
- 이 지시로 환각이 0이 되지는 않습니다. 애매하게 관련 있는 조각, 추론을 요구하는 질문에서 남습니다.
- 문서 조각을 통해서도 프롬프트 인젝션이 들어옵니다. 바깥에서 받은 문서를 인덱스에 넣을 때는 출처를 따집니다.
- 다섯 겹의 방어 — 검색(구조) · 근거 제한(확률) · 모를 때 할 말(확률) ·
temperature=0(확률) · 출처(사람 검증). 3장의 세 겹에 검색과 출처를 더한 것입니다. temperature=0— 근거대로만 말해야 하는 상황입니다.