실무 Multi-Agent 오케스트레이션 14장 · 근거 기반 답변 3 / 7 ← 이전목차다음 → TechLead Cro

14장. 근거 기반 답변

컨텍스트 주입과 출처 꼬리표 — 발췌를 어떻게 넣을 것인가

한 줄 요약

찾은 청크를 프롬프트에 넣는 일을 컨텍스트 주입(context injection) 이라고 합니다. 그냥 이어 붙이지 않고, 발췌마다 번호와 출처 꼬리표를 붙여 넣습니다. 모델은 답변에 사용한 발췌 번호를 고르고, 코드는 그 발췌의 메타데이터로 출처를 붙입니다.


1. 컨텍스트란

컨텍스트(context) 는 모델이 답을 쓸 때 참고하라고 프롬프트에 함께 넣어 주는 글입니다. RAG에서는 검색으로 찾은 청크들이 컨텍스트가 됩니다.

모델은 chroma_db/를 직접 들여다보지 못합니다. 모델이 볼 수 있는 것은 우리 코드가 프롬프트에 적어 준 글뿐입니다. 그러니 무엇을 어떤 모양으로 적어 주느냐가 답의 품질을 정합니다.


2. 나쁜 방법과 좋은 방법

나쁜 방법 — 그냥 이어 붙인다

context = "\n".join(d.page_content for d in docs)

청크 네 개가 한 덩어리 글이 됩니다. 어디까지가 한 발췌인지, 어느 문서의 몇 조인지 모델이 알 수 없습니다. 출처를 쓰라고 해도 쓸 이름이 없습니다.

좋은 방법 — 발췌마다 번호와 출처를 붙인다

context = "\n\n".join(
    f"[발췌 {i+1}] ({d.metadata.get('doc_title', '정책')} · "
    f"{d.metadata.get('article') or '조항 미상'})\n{d.page_content}"
    for i, d in enumerate(docs)
)

이렇게 만든 컨텍스트는 다음과 같은 모양이 됩니다.

[발췌 1] (반품교환환불정책 · 제5조 (환불 처리 기간), 제6조 (환불 금액 산정), 제7조 (반품·교환 불가 사유))
계좌이체·무통장 계좌 환급 검수 후 3일 이내
(중략)

[발췌 2] (반품교환환불정책 · 제8조 (배송 지연 보상), 제9조 (분쟁 처리), 자주 묻는 질문 (FAQ))
서 제외된다.
제9조 (분쟁 처리)
(중략)
붙인 것 어디서 왔나 왜 붙이나
[발췌 1] 코드가 매긴 번호 발췌 사이의 경계와 모델이 선택할 번호를 알려 줍니다
반품교환환불정책 메타데이터 doc_title 어느 문서인지. 정책 문서가 두 개이므로 필요합니다
제5조 …, 제6조 …, 제7조 … 메타데이터 article 이 발췌에 어느 조항들이 담겨 있는지

3. 꼬리표는 12장에서 심어 둔 것이다

doc_title과 article은 이번 장에서 만든 것이 아닙니다. 12장에서 청크를 만들 때 메타데이터로 붙여 두었고, 13장에서 벡터와 함께 chroma_db/에 저장했습니다. 그것이 지금 검색 결과에 딸려 나온 것입니다.

장 메타데이터에 한 일
12장 청크마다 문서 이름과 조항을 붙였다
13장 벡터와 함께 저장했다
14장 검색 결과에서 꺼내 꼬리표로 썼다

색인할 때 붙여 두지 않았다면 지금 붙일 방법이 없습니다. 출처를 밝히는 답변은 색인 단계에서 이미 정해집니다.


4. 꼬리표에 조항이 여럿일 때

12장에서 본 대로 청크 하나에는 조항이 두세 개 들어 있고, 꼬리표 article에는 그 조항이 모두 적혀 있습니다.

청크 꼬리표
policy-007 제3조 (반품 신청 절차), 제4조 (교환), 제5조 (환불 처리 기간)
policy-010 제8조 (배송 지연 보상), 제9조 (분쟁 처리), 자주 묻는 질문 (FAQ)

모델은 발췌 본문을 읽고 답변에 사용한 발췌 번호를 evidence_ids로 반환합니다. 코드는 그 번호에 해당하는 청크에서 doc_title과 article을 꺼내 그대로 붙입니다. 발췌 번호는 이번 검색 결과의 순서이며, DB의 고정 청크 ID와는 다릅니다.

예를 들어 policy-010을 선택했다면 다음과 같이 표시합니다.

(근거: 반품교환환불정책 · 제8조 (배송 지연 보상), 제9조 (분쟁 처리), 자주 묻는 질문 (FAQ))

article 전체를 표시하므로 실제 답변에 사용한 조항보다 출처 범위가 넓을 수 있습니다. 모델이 조항 이름을 다시 쓰지 않아 이름의 오타나 문서명 누락을 줄일 수 있습니다. 다만 선택한 청크가 답변을 실제로 뒷받침하는지는 원문과 대조해야 합니다.


5. 최종 프롬프트의 모양

컨텍스트 뒤에 고객의 질문을 붙이면 모델에게 보낼 글이 완성됩니다.

prompt = f"[정책 발췌]\n{context}\n\n[고객 문의]\n{question}"

[정책 발췌]와 [고객 문의]라는 제목으로 참고 자료와 질문의 자리를 나눴습니다. 3장에서 시스템 프롬프트를 [응대 원칙], [형식]으로 나눈 것과 같은 요령입니다.

모델에게 가는 글 전체를 자리별로 정리하면 이렇습니다.

자리 내용 누가 만드나
시스템 프롬프트 역할과 세 가지 규칙 (다음 절) 개발자가 미리 써 둔다
사용자 메시지 앞부분 [정책 발췌] + 꼬리표 붙은 청크 4개 코드가 질문마다 새로 만든다
사용자 메시지 뒷부분 [고객 문의] + 질문 고객

핵심 정리

  • 컨텍스트는 모델이 참고하도록 프롬프트에 넣어 주는 글입니다. 모델은 그 글만 볼 수 있습니다.
  • 발췌마다 번호와 출처 꼬리표를 붙여 넣습니다. 꼬리표가 인용할 이름이 됩니다.
  • 꼬리표는 12장에서 심은 메타데이터입니다. 색인할 때 없었으면 지금도 없습니다.
  • 모델은 근거 발췌 번호를 선택하고, 코드는 doc_title · article을 그대로 표시합니다. 청크에 담긴 조항 이름이 모두 표시됩니다.
  • 최종 프롬프트는 [정책 발췌] + [고객 문의] 입니다.
← 이전 절Retriever — 검색을 부품으로 만든다다음 절 →세 가지 규칙과 기권 — 없으면 없다고 말하게 한다
오명운 · macro@prag-ai.com