딥 리서치 에이전트 만들기

딥 리서치 에이전트 만들기 (Build a deep research agent)

연구 질문을 여러 단계로 쪼개고, 전문화된 sub-agent에게 위임한 뒤, 결과를 종합해 보고서로 만드는 웹 리서치 에이전트를 Deep Agents로 직접 만들어 볼게요. 팀 단위 병렬 리서치에서 각 컨텍스트를 격리하고 인용까지 잡아내는 패턴을 자연스럽게 익힐 수 있어요.

출처: 공식문서

개요 (Overview)

이 가이드에서 만들 에이전트는 이렇게 동작해요.

  1. opt-in todo list 미들웨어로 연구 계획 수립
  2. 컨텍스트를 격리한 sub-agents에게 연구 작업 위임
  3. 정보를 모으면서 검색 결과를 평가하고 다음 단계 계획
  4. 인용을 포함해 종합한 최종 보고서 작성

위임된 sub-agents는 Tavily로 웹 검색을 하고, 분석을 위해 전체 웹페이지 내용을 가져와요.

핵심 개념: Subagents(병렬·컨텍스트 격리 리서치), 커스텀 tools(웹 검색), opt-in planning tool(다단계 계획).

사전 준비 (Prerequisites)

  • Anthropic(Claude) 또는 Google(Gemini) API 키
  • Tavily 웹 검색 키 (선택, 무료 티어로 충분)
  • LangSmith tracing 키 (선택)

설정 (Setup)

mkdir deep-research-agent
cd deep-research-agent

의존성 설치 — Claude:

pip install deepagents tavily-python httpx markdownify langchain-anthropic langchain-core

Gemini:

pip install deepagents tavily-python httpx markdownify langchain-google-genai langchain-core

(uv를 쓸 땐 uv inituv add ...uv sync.)

API 키 세팅 — Claude: ANTHROPIC_API_KEY, TAVILY_API_KEY, (선택) LANGSMITH_API_KEY. Gemini: GOOGLE_API_KEY, TAVILY_API_KEY, (선택) LANGSMITH_API_KEY.

에이전트 만들기

프로젝트 디렉토리에 agent.py를 만들고 단계별로 채워볼게요.

1단계. 도구 추가tavily_search 도구는 Tavily로 URL을 발견한 뒤 전체 웹페이지 내용을 가져와 markdown으로 반환해요. 요약이 아니라 원문 전체를 분석할 수 있게 하는 거죠.

import os
from typing import Annotated, Literal

import httpx
from langchain.tools import InjectedToolArg, tool
from markdownify import markdownify
from tavily import TavilyClient

tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])


def fetch_webpage_content(url: str, timeout: float = 10.0) -> str:
    """Fetch webpage and convert HTML to markdown."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }
    try:
        response = httpx.get(url, headers=headers, timeout=timeout)
        response.raise_for_status()
        return markdownify(response.text)
    except Exception as e:
        return f"Error fetching {url}: {e!s}"


@tool(parse_docstring=True)
def tavily_search(
    query: str,
    max_results: Annotated[int, InjectedToolArg] = 1,
    topic: Annotated[
        Literal["general", "news", "finance"], InjectedToolArg
    ] = "general",
) -> str:
    """Search the web for information on a given query.

    Uses Tavily to discover relevant URLs, then fetches and returns full webpage content as markdown.

    Args:
        query: Search query to execute
        max_results: Maximum number of results to return (default: 1)
        topic: Topic filter - 'general', 'news', or 'finance' (default: 'general')

    Returns:
        Formatted search results with full webpage content
    """
    search_results = tavily_client.search(
        query,
        max_results=max_results,
        topic=topic,
    )
    result_texts = []
    for result in search_results.get("results", []):
        url = result["url"]
        title = result["title"]
        content = fetch_webpage_content(url)
        result_texts.append(f"## {title}\n**URL:** {url}\n\n{content}\n---")

    return f"Found {len(result_texts)} result(s) for '{query}':\n\n" + "\n".join(
        result_texts
    )

2단계. 프롬프트 추가 — 오케스트레이터 워크플로우와 sub-agent 프롬프트 템플릿을 agent.py에 넣어요. RESEARCH_WORKFLOW_INSTRUCTIONS는 계획→저장→위임→종합→보고→검증 워크플로우를, RESEARCHER_INSTRUCTIONS는 실제 리서처의 검색 예산과 중단 규칙을, SUBAGENT_DELEGATION_INSTRUCTIONS는 sub-agent 위임 전략(단일 우선, 명확한 비교에만 병렬화)을 담아요. (프롬프트 원문은 아주 길어서 여기서는 행동 원칙 위주로 요약했지만, 아래 agent.py 코드에서 RESEARCH_WORKFLOW_INSTRUCTIONS·RESEARCHER_INSTRUCTIONS·SUBAGENT_DELEGATION_INSTRUCTIONS 상수로 그대로 사용됩니다. 원문을 그대로 붙여 쓰시면 돼요.)

3단계. 작업 계획 활성화Task planning은 opt-in이에요. 연구 워크플로우는 write_todos로 질문을 작은 작업으로 쪼개므로 에이전트를 만들 때 TodoListMiddleware를 넘겨요.

from langchain.agents.middleware import TodoListMiddleware

4단계. 에이전트 생성 — 모델 초기화와 에이전트 생성을 agent.py에 추가해요. 공급자를 골라서 쓰면 됩니다. Claude 버전:

from datetime import datetime

from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware
from langchain.chat_models import init_chat_model

max_concurrent_research_units = 3
max_researcher_iterations = 3

current_date = datetime.now().strftime("%Y-%m-%d")

INSTRUCTIONS = (
    RESEARCH_WORKFLOW_INSTRUCTIONS
    + "\n\n"
    + "=" * 80
    + "\n\n"
    + SUBAGENT_DELEGATION_INSTRUCTIONS.format(
        max_concurrent_research_units=max_concurrent_research_units,
        max_researcher_iterations=max_researcher_iterations,
    )
)

research_sub_agent = {
    "name": "research-agent",
    "description": "Delegate research to the sub-agent. Give one topic at a time.",
    "system_prompt": RESEARCHER_INSTRUCTIONS.format(date=current_date),
    "tools": [tavily_search],
}

model = init_chat_model(model="anthropic:claude-sonnet-4-5-20250929", temperature=0.0)

agent = create_deep_agent(
    model=model,
    tools=[tavily_search],
    system_prompt=INSTRUCTIONS,
    subagents=[research_sub_agent],
    middleware=[TodoListMiddleware()],
)

Gemini 버전은 from langchain_google_genai import ChatGoogleGenerativeAI를 쓰고 model = ChatGoogleGenerativeAI(model="gemini-3-pro-preview", temperature=0.0)로 지정하면 돼요.

에이전트 실행하기

에이전트는 동기적으로(전체 결과를 기다렸다 출력) 또는 스트리밍으로(업데이트가 들어오는 대로) 실행할 수 있어요. agent.py 하단에 해당 코드를 추가하세요.

동기 실행:

from langchain.messages import HumanMessage

if __name__ == "__main__":
    result = agent.invoke(
        {
            "messages": [
                HumanMessage(
                    content="What are the main differences between RAG and fine-tuning for LLM applications?"
                )
            ]
        }
    )

    for msg in result.get("messages", []):
        if hasattr(msg, "content") and msg.content:
            print(msg.content)

스트리밍:

from langchain.messages import HumanMessage

if __name__ == "__main__":
    stream = agent.stream_events(
        {
            "messages": [
                HumanMessage(content="Compare Python vs JavaScript for web development")
            ]
        },
        version="v3",
    )
    for message in stream.messages:
        for token in message.text:
            print(token, end="", flush=True)

프로젝트 루트에서 실행:

python agent.py

실행 전에 LANGSMITH_API_KEY를 설정해 두면 LangSmith에서 multi-step trace를 보고 디버깅·모니터링할 수 있어요.

전체 코드: Deep Research 예제를 GitHub에서 봐 주세요.

다음 단계 (Next steps)

에이전트 파일의 프롬프트 상수를 바꿔 워크플로우·위임 전략·리서처 동작을 커스터마이즈할 수 있고, 위임 제한을 조정해 더 많은 병렬 sub-agent나 위임 라운드를 허용할 수도 있어요.

더 알아보기 (Learn more)

  • Subagents — 도구·프롬프트가 다른 subagent 설정법
  • Customization — 모델·도구·시스템 프롬프트·task planning 커스터마이즈
  • LangSmith — 리서치 실행 trace로 다단계 행동 디버깅
  • Deep Research Course — LangGraph로 deep research 전체 강의