logo

긴 컨텍스트와 웹 검색

Lost in the Middle 현상과 긴 컨텍스트 모델의 장단점을 RAG와 비교하고, 문서 통째로 넣기·PDF 입력·웹 검색으로 같은 일을 해 본다.

  • 직접 만든 RAG 말고도 모델에 외부 자료를 주는 방법이 있음.
    • 문서를 통째로 프롬프트에 넣기: 컨텍스트 창이 큰 모델이라면 검색 없이 전부 넣음.
    • 파일 입력: PDF 같은 파일을 API에 그대로 첨부.
    • 웹 검색: 검색할 자료를 미리 모아 두지 않고, 모델이 인터넷을 검색하게 함.
  • 어느 쪽이 나은지 판단하려면 긴 입력을 모델이 실제로 얼마나 잘 쓰는지 알아야 함.

Lost in the Middle

  • Liu 외(2023)의 실험: 질문과 문서 20개를 주고, 정답이 든 문서의 위치를 맨 앞부터 맨 뒤까지 바꿔 가며 정확도를 측정.
  • 결과: U자형 곡선. 정답 문서가 맨 앞이나 맨 뒤에 있을 때 정확도가 가장 높고, 중간에 있을 때 크게 떨어짐.
    • GPT-3.5-Turbo는 정답 문서가 중간에 있을 때의 정확도가 문서를 하나도 주지 않았을 때(56.1%)보다 낮았음.
    • 긴 입력을 받도록 확장한 모델도 같은 경향.
  • 사람의 기억에서 처음과 마지막을 잘 기억하는 초두 효과·최신 효과와 비슷한 모양.

출처: Liu 외(2023), Lost in the Middle: How Language Models Use Long Contexts.

  • 시사점:
    • 자료를 많이 넣는다고 좋아지지 않음. 검색 결과를 많이 넣으면 오히려 성능이 떨어질 수 있음.
    • 중요한 내용은 프롬프트의 앞이나 끝에 배치. 검색 결과는 관련성 높은 순으로 앞에 두고, 질문은 자료 뒤에 둠.
    • rerank로 프롬프트에 넣을 청크 수를 줄이는 것이 효과적인 이유.

긴 컨텍스트 모델과 RAG

  • 2024년 이후 Gemini, Claude 등은 100만 토큰 이상을 입력받음. 책 몇 권 분량.
  • 2024년 말에는 "창이 충분히 크면 전부 넣고 모델에 맡기면 되니 RAG는 필요 없다"는 주장도 나옴. 하지만 위의 Lost in the Middle처럼 긴 입력을 다루는 문제는 창이 커져도 남음. 12주 컨텍스트 엔지니어링에서 다시 다룸.
비교 항목 긴 컨텍스트에 전부 넣기 RAG
시스템 단순. 청킹·임베딩·검색 불필요 색인과 검색을 만들고 관리해야 함
여러 자료 사이의 관계 전체를 한꺼번에 보고 종합 가능 검색된 일부만 봄
자료의 양 컨텍스트 창 크기가 한계 거의 무한한 자료에서 검색
비용과 속도 질문마다 전체 토큰 비용. 캐싱으로 줄일 수 있음 필요한 청크만 보내 쌈
근거 추적 어느 부분을 썼는지 알기 어려움 검색된 청크로 바로 확인
긴 입력의 문제 중간 내용을 놓칠 수 있음 짧은 입력이라 영향이 적음

실습 준비

!pip install -q openai
from pathlib import Path
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=Path("openrouter-key.txt").read_text().strip(),
)
model = "openai/gpt-4.1-mini"
from mindmall_guide import guide

문서를 통째로 넣기

  • 마인드몰 안내서는 짧아서 통째로 넣어도 됨. 검색 단계가 없음.
  • 자료를 앞에, 질문을 끝에 둠.
question = "실버 회원이 제주도로 4만 원어치를 주문하면 배송비는 얼마인가요?"

prompt = f"""다음 안내서만 근거로 질문에 답하세요.

[안내서]
{guide}

[질문]
{question}"""

response = client.chat.completions.create(model=model, messages=[{"role": "user", "content": prompt}])
print(response.choices[0].message.content)
실행 결과
실버 회원이 제주도로 4만 원어치를 주문하는 경우를 안내서에 따라 계산해 보면:

- 결제 금액 4만 원은 5만 원 미만이므로 기본 배송비 3,000원이 발생합니다.
- 제주도는 결제 금액과 상관없이 추가 배송비 3,000원이 붙습니다.
- 실버 회원은 매월 무료 배송 쿠폰 2장을 받으나, 질문에서 쿠폰 사용 여부가 언급되지 않았으므로 제외합니다.

따라서, 배송비는  
3,000원(기본 배송비) + 3,000원(제주도 추가 배송비) = 6,000원입니다.

답: 배송비는 6,000원입니다.
  • 여러 절(배송비, 제주도 추가 배송비, 실버 회원의 무료 배송 쿠폰)을 한꺼번에 보고 종합할 수 있음. 청크 3개만 검색하는 RAG라면 그중 하나를 놓칠 수 있음.
  • 입력 토큰(response.usage.prompt_tokens)은 안내서 전체 분량. 위 실행에서 1,174토큰. 안내서가 수백 쪽이라면 질문마다 수십만 토큰을 보내야 하고, 창에 다 들어가지도 않음.

PDF 파일 입력

  • OpenRouter는 PDF를 메시지에 첨부하는 기능을 제공. 텍스트 추출이나 청킹 없이 파일째 질문.
  • 메시지 내용을 목록으로 만들고, 텍스트와 파일을 각각 항목으로 넣음.
  • file_data: PDF의 인터넷 주소 또는 base64로 인코딩한 파일 내용.
  • plugins: PDF를 어떻게 읽을지 지정하는 OpenRouter 고유 옵션. openai 라이브러리에 없는 인자라 extra_body로 전달.
    • cloudflare-ai: PDF를 텍스트로 바꿔 모델에 전달. 무료.
    • mistral-ocr: 스캔한 문서처럼 이미지로 된 PDF를 글자 인식(OCR)으로 읽음. 유료.
    • native: PDF를 직접 받는 모델이면 모델에 그대로 전달.

Lost in the Middle 논문을 첨부해 질문.

response = client.chat.completions.create(
    model=model,
    messages=[{
        "role": "user",
        "content": [
            {"type": "text",
             "text": "이 논문에서 관련 문서의 위치에 따라 성능이 어떻게 달라졌는지 세 문장으로 요약하세요."},
            {"type": "file",
             "file": {"filename": "lost-in-the-middle.pdf",
                      "file_data": "https://arxiv.org/pdf/2307.03172"}},  # PDF 주소
        ],
    }],
    extra_body={"plugins": [{"id": "file-parser", "pdf": {"engine": "cloudflare-ai"}}]},
)
print(response.choices[0].message.content)
실행 결과
이 논문에서 관련 문서의 위치에 따른 성능 변화는 다음과 같이 요약할 수 있습니다.

1. 언어 모델의 성능은 관련 문서가 입력 맥락의 맨 앞(primacy bias)이나 맨 뒤(recency bias)에 위치할 때 가장 높게 나타나고, 중간 위치에 있을 때는 성능이 크게 저하되는 U자형 곡선 패턴을 보였다.
2. 특히 GPT-3.5-Turbo 모델의 경우, 관련 정보가 중간에 위치했을 때는 문서가 아예 없는 closed-book 상황보다도 낮은 성능을 보여, 모델이 긴 맥락 내에서 중간 위치 정보를 효과적으로 활용하지 못함을 확인했다.
3. 맥락 길이나 모델의 확장된 맥락 길이 여부와 무관하게 이러한 위치 편향 현상이 나타났으며, 확장된 맥락을 가진 모델도 본래 모델보다 중간 위치 정보를 더 잘 활용하지 못하는 경우가 많았다.
  • 논문 전체가 입력 토큰으로 들어감. 위 실행에서 17,429토큰. 이 모형으로는 질문 하나에 1센트 남짓이지만, 질문할 때마다 같은 비용이 반복됨.
  • 내 컴퓨터의 PDF는 base64로 인코딩해 "data:application/pdf;base64,..." 형식의 문자열로 file_data에 넣음.
  • 파일 하나에 대한 간단한 질문이라면 RAG를 직접 만드는 것보다 편리. 파일이 많거나 같은 파일에 질문을 반복한다면 RAG가 경제적.

웹 검색

  • 최신 정보나 공개된 자료가 필요할 때. 검색할 문서를 미리 모을 필요가 없음.
  • OpenRouter의 웹 검색 서버 도구: tools에 {"type": "openrouter:web_search"}를 넣으면 모델이 필요할 때 검색하고, 검색 결과를 읽고 답함.
  • 함수 호출과 달리 검색 실행은 OpenRouter 서버가 담당. 애플리케이션이 도구를 실행해 결과를 돌려줄 필요 없음.
  • 비용: 모델 토큰 비용에 더해 검색 요청마다 요금이 붙음.
response = client.chat.completions.create(
    model=model,
    messages=[{"role": "user",
               "content": "Anthropic이 발표한 Contextual Retrieval은 검색 실패율을 얼마나 줄였나요? 출처를 밝혀 짧게 답하세요."}],
    tools=[{"type": "openrouter:web_search",
            "parameters": {"max_results": 3}}],  # 검색 결과는 최대 3개
)
message = response.choices[0].message
print(message.content)
실행 결과
Anthropic의 Contextual Retrieval 기법은 검색 실패율을 최대 67%까지 감소시켰습니다. ([anthropic.com](https://www.anthropic.com/research/contextual-retrieval?utm_source=openai)) 
  • 답변의 근거가 된 웹 페이지는 annotations에 url_citation으로 담겨 옴.
[(a.url_citation.url, a.url_citation.title)
 for a in (message.annotations or []) if a.type == "url_citation"]
실행 결과
[('https://www.anthropic.com/research/contextual-retrieval?utm_source=openai',
  'Contextual retrieval in AI systems \\ Anthropic')]
  • 확인: 답변의 수치가 인용한 페이지에 실제로 있는가? 검색 결과에 요약 글이나 잘못된 정보가 섞일 수 있으므로 원문 출처인지도 확인.
  • 모델이 검색하지 않고 답할 수도 있음. 검색이 꼭 필요한 질문인지, annotations가 비어 있지 않은지 확인.

참고: OpenRouter PDF 입력, OpenRouter 웹 검색.

어떤 방법을 쓸까

상황 알맞은 방법
짧은 문서 하나, 전체를 종합하는 질문 통째로 넣기
PDF 한두 개에 대한 일회성 질문 파일 입력
사내 문서처럼 많고, 자주 바뀌고, 출처가 중요한 자료 RAG
최신 소식이나 공개 웹 자료 웹 검색
  • 함께 쓸 수도 있음. 예: 사내 문서는 RAG로, 최신 시장 정보는 웹 검색으로 찾아 한 프롬프트에 넣음.
  • 어떤 방법이든 결국 모델의 컨텍스트에 무엇을 넣을지 정하는 일. 12주 컨텍스트 엔지니어링으로 이어짐.

퀴즈

문제 1 / 5맞음: 0힌트: 0틀림: 0채점중: 0남음: 5

Lost in the Middle 실험에서 정확도가 가장 낮았던 경우는?

  • ○정답이 든 문서가 입력의 중간에 있을 때
  • ○정답이 든 문서가 입력의 맨 앞에 있을 때
  • ○정답이 든 문서가 입력의 맨 뒤에 있을 때
  • ○질문이 자료 뒤에 있을 때

퀴즈를 풀려면 대화형 기능을 불러와야 합니다.