LangChain Blog출처 게시일

컨텍스트 엔지니어링은 검색에서 시작된다

오픈 북 시험을 치르는 상상을 해보세요: 이론상으로는 모든 답을 갖고 있지만, 이상하게도 그것이 오히려 답을 찾는 일을 더 어렵게 만듭니다. 올바른 답을요.

핵심 요약

  • 에이전트는 제한된 컨텍스트 윈도우로 작업하며, 정보 검색은 세 가지 방식으로 실패합니다: 비효율적인 검색, 한꺼번에 너무 많은 컨텍스트, 그리고 관련 없거나 오래된 컨텍스트.
  • Deep Agents 하니스는 기본적으로 검색 규모를 작게 유지합니다. 내장 파일시스템 도구는 읽기 전에 검색합니다: glob은 파일 이름을 매칭하고, grep은 매칭되는 줄을 찾으며, read_file은 한 번에 한 페이지씩 읽습니다. 지나치게 큰 도구 결과는 오프로드됩니다: 전체 결과는 파일로 저장되지만 에이전트는 경로와 미리보기만 받습니다.
  • 커스텀 검색 도구(예: RAG)의 경우 "검색, 오프로드, 위임"을 사용하세요: 결과를 파일로 저장하고, 경로를 반환하며, 서브에이전트가 이를 읽도록 합니다. 메인 에이전트는 관련 있는 내용만 봅니다.

오픈 북 시험을 치르는 상상을 해보세요: 이론상으로는 모든 답을 갖고 있지만, 이상하게도 그것이 오히려 답을 찾는 일을 더 어렵게 만듭니다. 올바른 답을요.

  • 대체로 어디서부터 검색을 시작해야 할까요?
  • 출처가 올바르다는 것을 언제 알 수 있을까요?
  • 중복되는 검색에 에너지를 모두 쏟아붓지 않으려면 시간을 어떻게 최적화해야 할까요?

효율적으로 올바른 정보를 찾는 일은 그리 단순하지 않습니다.

에이전트에게 질문을 하나 건네면 비슷한 딜레마에 놓이게 됩니다: 에이전트는 제한된 컨텍스트 윈도우를 갖고 있기 때문에, 우리 인간처럼 특정 작업을 완료하려면 정보를 검색 해야 합니다(모든 것을 암기하고 있는 것이 아니라면요). 검색할 때 에이전트는 다음과 같은 문제에 부딪힙니다:

  • 비효율적인 검색: 모든 검색에는 토큰과 시간이 듭니다. 어설프게 검색하는 에이전트(예: 내용을 확인하려고 모든 파일을 열어보거나, 같은 쿼리를 계속 다르게 표현하는 경우)는 유용한 것을 찾기도 전에 검색 결과로 컨텍스트를 채워버릴 수 있습니다.
  • 한꺼번에 너무 많은 컨텍스트: 전체 데이터 내보내기 결과나 수십 개의 문서를 한 번에 쏟아내는 검색 하나만으로 컨텍스트 윈도우가 가득 차서, 정작 작업 자체를 할 공간이 없어집니다.
  • 관련 없거나 오래된 컨텍스트: 오래되었거나 주제에서 벗어난 정보는 모델에게 올바른 답만큼이나 설득력 있어 보일 수 있습니다. 일단 컨텍스트 윈도우에 들어오면 에이전트는 그것을 기반으로 작업을 진행할 수 있으며(그리고 그 실수는 이후 모든 단계로 이어집니다).

다행히도 에이전트가 올바른 정보를 검색할 수 있게 해주는 몇 가지 기법이 있습니다. 이러한 기법들을 전략적으로 적용하여 주어진 작업에 적시에 올바른 컨텍스트를 모델에게 제공하는 것이 바로 에이전트 하니스 의 역할입니다.

이 블로그에서는 에이전트 검색의 문제점(당면한 작업에 필요한 정보만 찾아서 가져오는 것), 이를 해결하기 위한 기법들, 그리고 에이전트 하니스를 설계할 때 이를 어떻게 염두에 두어야 하는지(예시로 LangChain의 Deep Agents 하니스를 사용)를 다룹니다.

컨텍스트가 일단 컨텍스트 창에 들어간 후 이를 관리하는 방법에 대해 더 알아보려면 이 컨텍스트 엔지니어링 블로그를 확인하세요.

우리의 예: 경쟁 분석 에이전트

이것이 실제 사용 사례인 경쟁 분석 에이전트에 어떻게 적용될 수 있는지 살펴보겠습니다. 매주 월요일, 에이전트는 수백 개의 경쟁사를 조사하고 팀의 템플릿에 따라 보고서를 작성합니다. 에이전트는 다음을 수행해야 합니다:

  • 지난주 Company A에 대한 노트 찾기
  • 방대한 시장 데이터 내보내기 파일에서 특정 매출 숫자 찾기
  • 뉴스 아카이브에서 새로운 소식 검색

이 각각은 서로 다른 종류의 검색이며 따라서 서로 다른 문제에 직면합니다. 우리는 에이전트를 Deep Agents 하네스 로 구축했기 때문에 이러한 문제를 최소화하기 위해 즉시 적용할 수 있는 몇 가지 기법이 있습니다.

1. 비효율적인 검색

우리의 에이전트는 각 경쟁사에 대해 세 가지를 찾아야 합니다. 각각에 대해 수십 번의 시도가 필요하다면(내용을 확인하기 위해 파일을 열거나, 소스가 다른 단어를 사용해서 검색어를 바꾸는 등), 보고서를 한 단어 쓰기도 전에 컨텍스트 창이 실패한 검색들로 가득 차버립니다.

따라서 최선의 검색은 에이전트가 반복할 필요가 없는 검색이며, 이는 각 작업에 적합한 도구를 사용하는 것을 의미합니다.

내장 파일시스템 도구부터 시작하기

에이전트가 정보를 검색하는 가장 일반적인 방법 중 하나는 파일시스템을 사용하는 것입니다. 그러나 에이전트가 내용을 확인하기 위해 모든 파일을 열어본다면 매우 비효율적으로 검색하는 것입니다. 읽는 각 파일이 컨텍스트 창에 들어가기 때문에, 그냥 살펴보기만 해도 컨텍스트 창이 가득 찰 수 있습니다.

더 나은 접근 방식은 먼저 이름 과 키워드 를 확인하는 것입니다(개방형 시험에서 모든 페이지를 읽는 대신 책의 색인을 훑어보는 방식처럼). 두 가지 일반적인 파일시스템 도구가 정확히 그 일을 합니다:

  • glob 는 이름 패턴으로 파일을 찾습니다. 예를 들어, **/company-a*.md 는 지난주 Company A에 대한 노트를 찾습니다.
  • grep 는 파일을 컨텍스트 창에 로드하지 않고 파일 내부를 검색합니다. 리터럴 텍스트를 매칭하며 매칭된 파일, 매칭된 줄, 또는 개수를 반환할 수 있습니다.

모든 딥 에이전트에는 다음이 함께 제공됩니다 이 도구들이 내장되어 있습니다, 나란히 ls, read_file, write_file 그리고 edit_file. 이 도구들은 파일이 어디에 있든(에이전트 상태, 로컬 디스크, 영구 저장소 또는 샌드박스) 동일하게 작동하는데, 그 이유는 모두 에이전트의 백엔드.

키워드만으로는 부족할 때 검색 도구를 추가하세요

Grep 찾을 단어를 알고 있을 때는 유용하지만, 일치시키는 것은 다음과 같은 것에 한정됩니다 정확한 텍스트로 제한됩니다. 이는 예시의 세 번째 작업, 즉 뉴스 아카이브에서 새로운 내용을 검색하는 작업에서 문제가 됩니다. 우리 에이전트가 "Company A funding"으로 grep을 실행하면 "Company A closes $50M Series B"라는 제목의 기사를 놓치게 되는데, 바로 그것이 찾으려던 내용임에도 불구하고 그렇습니다.

정확한 단어 대신 의미로 검색하려면(시맨틱 검색), 다음을 추가합니다 검색 도구 지원받는 벡터 스토어. 검색 도구(retrieval tool)는 에이전트가 정보를 가져오기 위해 호출할 수 있는 도구로, 질의를 받아 일부 데이터 소스에서 조회한 후 찾은 결과(가장 유사한 항목)를 반환합니다.

검색 도구가 어떻게 매칭하는지는 그 뒤에 있는 데이터 소스에 따라 달라집니다. 벡터 스토어에 기반한 경우 의미를 기준으로 매칭하지만, 검색 도구는 다른 데이터 소스 위에도 올라갈 수 있습니다:

  • SQL 데이터베이스(구조화된 조회용, 예: 판매 수치나 고객 기록)
  • 검색 API (웹에서 신선한 정보를 가져오는 용도)
  • 어느 MCP 서버 (다른 사람이 이미 만들어 놓은 도구의 경우, 예를 들어 문서 검색 같은 것)

시맨틱 검색이 작동하는 방식을 더 깊이 살펴보려면 다음을 참조하세요. LangChain 가이드.

에이전트가 언제 검색할지 결정하도록 허용하기

많은 검색 설정은 처음에 한 번 검색한 후 답변합니다. 이는 FAQ 봇(2단계 RAG), 하지만 우리 에이전트에게는 그렇지 않습니다. Company C를 조사하는 도중에 이전에 알지 못했던 제품 출시에 관한 기사를 발견했다고 가정해 봅시다. 이제 에이전트는 해당 제품의 가격 정보가 필요해집니다(처음부터 이를 찾아야 한다는 것을 알았을 수는 없었죠). 검색 기회가 한 번뿐이라면, 이 정보가 빠진 채로 보고서가 나가게 됩니다.

리서치 에이전트는 필요 에이전틱 검색(agentic retrieval) (2단계 RAG 대신): 에이전트가 언제 검색할지, 무엇을 검색할지, 더 검색이 필요한지 스스로 결정합니다. Deep Agents에서는 기본적으로 이미 이런 방식으로 작동합니다: 에이전트가 루프로 실행되며 필요한 만큼 몇 번이고 도구를 호출할 수 있습니다.

검색 노하우를 스킬로 패키징하세요

에이전트가 잘 검색하도록 돕기 위해, 검색 노하우를 skill로 패키징할 수 있습니다. skill이란 특정 작업을 위한 지침 모음입니다(예: 어떤 인덱스를 사용할지, 쿼리를 어떻게 표현할지, 인용을 어떻게 형식화할지).

에이전트는 작업이 필요할 때까지 각 skill의 이름과 짧은 설명만 보다가, 필요해지면 전체 지침을 로드합니다. 그래서 틈새 노하우는 필요할 때 존재하면서도 나머지 시간에는 컨텍스트를 차지하지 않습니다(이 기법을 progressive disclosure라고 합니다). 다음은 비디오 설명.

Progressive Disclosure (animated)

2. 한꺼번에 너무 많은 컨텍스트

그러나 검색 과정을 최적화했음에도 불구하고, 심지어 올바른 검색조차 너무 많은 결과를 반환할 수 있습니다. 우리 에이전트는 시장 데이터 내보내기 파일에서 단 하나의 숫자(B회사의 분기 매출)만 필요하지만, 그것을 찾기 위해 40,000개 행 전체를 로드하면 컨텍스트 창이 가득 차버립니다. 검색 도구도 같은 문제가 있습니다. 모든 뉴스 기사를 모델에 바로 반환하면, 5개 회사에 대해 검색당 4개 결과씩이면 금방 쌓입니다.

큰 파일을 한 번에 한 페이지씩 읽기

파일시스템 도구들은 가능한 한 최소한만 반환하도록 설계되었습니다. read_file 는 파일을 한 번에 한 페이지씩(기본값 100줄) 읽으며, 에이전트는 오프셋(시작할 줄)과 limit(읽을 줄 수)을 설정해 필요한 부분으로 바로 이동할 수 있습니다. 따라서 B회사의 매출을 찾으려면, 에이전트가 grep으로 찾은 다음 일치 항목 주변의 몇 줄만 읽습니다.

하네스가 과대한 결과를 오프로드하도록 하기

때로는 도구가 예상보다 많은 것을 반환하기도 합니다. 도구 결과가 너무 클 때(약 20,000 토큰 초과), 하네스는 그것을 /large_tool_results/ 아래의 파일로 저장하고 에이전트에게 경로와 짧은 미리보기(처음과 마지막 몇 줄)를 제공합니다. 그런 다음 에이전트는 read_file의 offset과 limit을 사용해 필요한 부분만 다시 읽을 수 있습니다.

검색 도구가 파일 경로를 반환하도록 하기

크기 한도를 기다리지 않고, 같은 아이디어를 자신의 검색 도구에 적용할 수 있습니다. 각 결과를 파일에 쓰고 경로만 반환하도록 하는 것입니다. 이것은 “retrieve, offload, delegate”라는 패턴의 첫 번째 부분입니다(LangChain의 RAG 가이드 에서 전체를 다룹니다).

간소화된 검색 도구입니다. 검색을 실행하고, 각 결과를 에이전트의 백엔드에 파일로 저장한 뒤, 파일 경로 목록만 반환합니다:

backend = StateBackend()

@tool
def search_news(query: str) -> str:
    """Search the news archive and save each result to a file."""
    docs = vector_store.similarity_search(query, k=4)
    files = [(f"/retrieved/result_{i}.md", doc.page_content.encode()) for i, doc in enumerate(docs)]
    backend.upload_files(files)
    return "Saved results to: " + ", ".join(path for path, _ in files)

이제 메인 에이전트는 네 개의 전체 기사 대신 네 개의 짧은 경로를 봅니다.

3. 관련 없거나 오래된 컨텍스트

결과가 작다고 해도 그것이 잘못된 결과라면 도움이 되지 않습니다. 그 네 개의 파일 중 어떤 것이라도 비슷한 이름을 가진 다른 회사에 관한 것이거나 2년 전 기사일 수 있습니다. 주제에서 벗어나거나 오래된 텍스트는 모델에게 올바른 답과 똑같이 그럴듯해 보이므로, 목표는 그런 텍스트를 메인 에이전트의 컨텍스트에서 완전히 배제하는 것입니다.

서브에이전트가 결과를 필터링하도록 하기

하지만 메인 에이전트는 여전히 어떤 파일이 중요한지 알아내기 위해 그 파일들을 읽어야 합니다(열 때마다 주제에서 벗어난 기사가 컨텍스트에 들어옵니다). 바로 여기서 “검색, 오프로드, 위임” 패턴의 “위임” 부분이 등장합니다: 서브에이전트 가 검색된 각 파일을 읽고 핵심 사실만 반환합니다.

  1. 서브에이전트 정의: 설명은 메인 에이전트에게 언제 이 서브에이전트를 사용할지 알려주고(호출당 하나의 파일), 시스템 프롬프트는 서브에이전트에게 그 파일로 무엇을 할지 알려줍니다.
chunk_analyst = {
		"name": "chunk-analyst",
		"description": "Analyze one retrieved file. Pass the question and a single path under /retrieved/.",
		"system_prompt": "Use read_file to read the assigned file. Return the key facts and the source URL.",
}
  1. 메인 에이전트에게 검색 도구와 서브에이전트를 제공합니다. search_news에서 사용한 것과 동일한 백엔드를 전달하여, 서브에이전트가 도구가 저장한 파일을 읽을 수 있도록 합니다.
agent = create_deep_agent(
		model=model,
		tools=[search_news],
		backend=backend,  # same instance, so read_file can see /retrieved/
		subagents=[chunk_analyst],
)

이제 흐름은 다음과 같습니다:

  1. 검색: 메인 에이전트가 search_news 를 호출하고 네 개의 경로를 받습니다.
  2. 위임: 각 경로를 청크 분석가(chunk-analyst) 서브에이전트에 보내며, 이들은 병렬로 실행될 수 있습니다.
  3. 종합: 각 분석가가 출처와 함께 짧은 요약을 반환합니다. 메인 에이전트는 이를 결합하고, 빠진 내용이 있으면 다시 검색합니다.

원시 결과는 파일로 가고, 시끄러운 읽기 작업은 자체 컨텍스트 윈도우를 가진 서브에이전트에서 일어나며, 메인 에이전트는 결론만 봅니다. 주제에서 벗어난 내용은 서브에이전트의 컨텍스트에 남고, 메인 에이전트의 컨텍스트 윈도우는 깨끗하게 유지됩니다!

최신 소스에서 가져오기

필터링은 주제에서 벗어난 결과를 걸러주지만, 오래된 결과는 고칠 수 없습니다. 우리의 경쟁 분석 에이전트는 매주 월요일에 실행되므로, 지난달의 인덱스로는 이번 주 뉴스를 잡아내지 못합니다.

한 가지 해결책: 검색 API나 MCP 서버가 에이전트에게 현재 정보를 제공할 수 있습니다(몇 달 전에 인덱싱된 내용 대신). 데이터에 날짜가 있다면, 검색 도구에서 날짜로 필터링하는 것도 오래된 결과를 걸러내는 데 도움이 됩니다.

검색된 텍스트를 주의하기

반대로, 관련성 있는 최신 결과가 문제를 일으킬 수도 있습니다! 검색된 텍스트는 데이터이지 지시 사항이 아니므로, 검색하는 어떤 것에도 프롬프트 인젝션 (그리고 어떤 프롬프트나 구분자 전략으로도 이를 완전히 막을 수 없습니다). 출력물을 반드시 검증하세요: 답변이 예상한 출처를 인용하는지, 그리고 주장이 그 출처와 일치하는지 확인하세요.

기억해 둘 만한 검색 전략

다음과 같은 점을 주의하면 됩니다(어떤 하네스를 사용하든 관계없이):

  • 작동하는 가장 효율적인 도구를 사용하십시오. 단어를 알고 있다면 grep이 임베딩 조회보다 낫습니다. 정말 필요할 때에만 시맨틱 검색을 사용하세요.
  • 페이지가 로드되는 대신. 파일을 한 번에 모두 읽지 말고 조각 단위로 읽으세요.
  • 페일로드 대신 포인터를 반환하십시오. 검색된 콘텐츠를 파일에 저장하고 그 경로를 에이전트에게 전달하십시오.
  • 읽기 위임하기. 서브에이전트가 읽고 요약하게 하여 메인 에이전트가 결론만 보도록 하십시오.
  • 접지 확인. 검색된 내용과 답변을 대조하고, 검색된 텍스트는 신뢰할 수 없는 것으로 취급하십시오.

사용해 보기

uv add deepagents

라이브 강사에게서 더 배우고 싶으신가요? 저는 11월 9일에 이 내용을 다룰 예정입니다: 등록 여기!

‍

검색은 이야기의 절반에 불과합니다: 정보가 에이전트에 전달되는 방식이 남은 부분입니다. 시간이 지나면서 정보가 쌓이기 시작할 때 에이전트의 컨텍스트 윈도우를 어떻게 관리해야 할까요?

자세히 알아보려면 다음을 확인하세요:

원문 출처

LangChain Blog

내용 안내

원문 발행 및 권리는 출처에 있습니다.

기계 번역 · 원문을 참고하세요