Deep Agents 커스터마이징
Deep Agents 커스터마이징 (Customize Deep Agents)
create_deep_agent는 프로덕션에 바로 쓸 수 있는 기반을 제공해요. 이제 그 하네스를 우리의 목표에 맞게 감싸는 단계지요—데이터를 연결하고, 동작을 다듬고, 사용 사례에 필요한 기능을 더하면 돼요. 시스템 프롬프트부터 툴, 서브에이전트, 미들웨어까지 커스터마이징 포인트가 잘 정리되어 있으니 하나씩 살펴볼게요.
출처: 공식문서
from deepagents import create_deep_agent
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
system_prompt="You are a helpful assistant.",
tools=[search, fetch_url],
memory=["./AGENTS.md"],
skills=["./skills/"],
)
모델 문자열만 바꾸면 다른 프로바이더에서도 같은 구조로 동작해요(openai:gpt-5.5, anthropic:claude-sonnet-4-6, openrouter:z-ai/glm-5.2, fireworks:accounts/fireworks/models/glm-5p2, baseten:zai-org/GLM-5.2, ollama:north-mini-code-1.0).
주요 파라미터는 다음과 같아요.
| 파라미터 | 역할 |
|---|---|
model= |
어떤 모델을 쓸지 |
system_prompt= |
에이전트에 줄 커스텀 지시 |
tools= |
에이전트가 호출할 도메인 툴 |
memory= |
시작 시 로드하는 AGENTS.md 파일 |
skills= |
온디맨드 지식을 위한 skills 디렉토리 |
backend= |
파일시스템 백엔드(기본값 StateBackend) |
permissions= |
파일시스템의 경로 수준 접근 제어 |
subagents= |
위임 작업을 위한 커스텀 서브에이전트 |
middleware= |
Deep Agents 스택에 병합되는 추가 미들웨어 |
interrupt_on= |
인간 승인을 위해 툴 호출 전 일시 중지 |
response_format= |
구조화 출력 스키마 |
state_schema= |
커스텀 그래프 상태 스키마 |
context_schema= |
런당 런타임 컨텍스트 스키마(user ID, API 키, 피처 플래그) |
| profiles | 모델별 기본 설정을 재사용 가능한 번들로 |
전체 파라미터 목록은 create_deep_agent API 레퍼런스, 처음부터 완전히 커스텀 하네스를 짜는 방법은 Configure the harness나 Build a deep agent from scratch 가이드를 참고하세요. 툴·서브에이전트·백엔드를 추가할 때는 LangSmith로 각 요소가 어떻게 함께 동작하는지 추적해 보세요. observability quickstart로 세팅하면 되고, LangSmith 배포는 Going to production 참고. 트레이스를 모니터링하고 문제를 감지·제안하는 LangSmith Engine도 함께 설정하는 걸 권장해요.
Model
model에는 provider:model 형식의 문자열이나 초기화된 모델 인스턴스를 넘겨요. 지원 프로바이더 전체는 supported models, 테스트된 추천은 suggested models를 참고하세요. provider:model 형식(예: openai:gpt-5.5)을 쓰면 모델을 빠르게 전환할 수 있어요.
OpenAI 예시를 보면 세 가지 방식이 있어요—OPENAI_API_KEY를 설정한 뒤 ① 문자열로 바로 create_deep_agent(model="openai:gpt-5.5"), ② 특정 모델 파라미터가 필요하면 init_chat_model로 초기화해 create_deep_agent(model=model), ③ 직접 모델 클래스를 만들어 전달하는 방식이에요.
import os
from langchain.chat_models import init_chat_model
from deepagents import create_deep_agent
os.environ["OPENAI_API_KEY"] = "sk-..."
model = init_chat_model(model="openai:gpt-5.5")
agent = create_deep_agent(model=model)
다른 프로바이더도 비슷한 패턴을 따라요. Anthropic은 langchain[anthropic](+ ANTHROPIC_API_KEY)과 init_chat_model(model="claude-sonnet-4-6"), Azure는 langchain[openai](+ AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, OPENAI_API_VERSION="2025-03-01-preview")와 model="azure_openai:gpt-5.5", Google Gemini는 langchain[google-genai]를 쓰는 식이에요. chat 모델은 일시적 API 실패를 지수 백오프로 자동 재시도해요—max_retries/timeout 튜닝 기본값·한계·샘플은 LangChain Models 페이지에 있어요.
Tools
내장 툴(파일 관리·서브에이전트 생성) 외에도 커스텀 툴을 제공할 수 있어요. 함수를 정의해 tools=[...]에 넘기면 돼요. Tavily 검색 툴 예시를 보면 이런 식이에요.
import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(
query: str,
max_results: int = 5,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = False,
):
"""Run a web search"""
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
tools=[internet_search],
)
MCP 툴
Deep Agents는 Model Context Protocol (MCP) 툴을 완전히 지원해요. 데이터베이스·API·파일시스템 등 어떤 MCP 서버에서든 툴을 로드해 create_deep_agent에 바로 넘길 수 있어요. MCP 서버에 연결하려면 LangChain을 mcp 엑스트라로 설치하고, MCPAdapter로 툴을 가져와요.
pip install "langchain[mcp]"
import asyncio
from deepagents import create_deep_agent
from langchain.mcp import MCPAdapter
async def main():
config = {"mcpServers": {"my_server": {"url": "http://localhost:8000/mcp"}}}
async with MCPAdapter(config) as adapter:
tools = await adapter.list_tools()
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
tools=tools,
)
await agent.ainvoke(
{
"messages": [
{"role": "user", "content": "Use the MCP server to help me."}
]
},
config={"configurable": {"thread_id": "1"}},
)
System prompt
system_prompt=으로 에이전트에 우리의 지시를 줄 수 있어요.
from deepagents import create_deep_agent
research_instructions = """\
You are an expert researcher. Your job is to conduct \
thorough research, and then write a polished report. \
"""
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
system_prompt=research_instructions,
)
문자열 외에도 구조화 content blocks을 가진 SystemMessage를 받아요. Deep Agents는 그 블록을 보존해요(서브에이전트 dict 스펙은 문자열만). 선언적 서브에이전트는 자기 모델에 맞는 프로파일 오버레이를 해석한 뒤, 해석된 프로파일의 base_system_prompt/system_prompt_suffix를 서브에이전트가 작성한 system_prompt에 적용해요. system_prompt_suffix만 제공하는 프로파일은 프롬프트에 이어붙이고, base_system_prompt를 설정하면 통째로 교체해요.
자동 추가되는 general-purpose 서브에이전트의 기본 프롬프트는 general_purpose_subagent.system_prompt(설정 시) → HarnessProfile.base_system_prompt(설정 시) → SDK general-purpose 기본값 순으로 결정되고, 프로파일 접미사가 그 위에 겹쳐져요. 두 오버라이드 필드가 모두 있으면 general-purpose 전용이 이겨서, 둘을 조정하는 호출자가 GP 오버라이드를 조용히 잃지 않아요.
from deepagents import (
GeneralPurposeSubagentProfile,
HarnessProfile,
register_harness_profile,
)
register_harness_profile(
"anthropic",
HarnessProfile(
base_system_prompt="You are ACME's support orchestrator.", # main agent
general_purpose_subagent=GeneralPurposeSubagentProfile(
system_prompt="You are a research subagent. Cite sources.", # GP subagent
),
system_prompt_suffix="Always think step by step.",
),
)
| 스택 | 최종 시스템 프롬프트 |
|---|---|
| Main agent | "You are ACME's support orchestrator." + SUFFIX |
| GP subagent | "You are a research subagent. Cite sources." + SUFFIX |
Middleware
Deep Agents는 아래 나열된 내장 미들웨어, LangChain의 prebuilt 미들웨어, 프로바이더 특화 미들웨어, 직접 작성하는 커스텀 미들웨어 등 모든 미들웨어를 지원해요. create_deep_agent의 middleware 인자로 전달하면, 각 인스턴스는 .name을 스택에 이미 있는 내장 항목과 대조해 병합돼요—일치하면 그 자리를 교체하고, 일치하지 않으면 PatchToolCallsMiddleware 뒤에 삽입돼요.
Deep Agents 스택
create_deep_agent는 고정된 순서로 미들웨어를 쌓아요. bare stack은 모델만 줄 때 얻는 것이고, full stack은 옵션 인자를 넘기거나 해석된 harness profile이 기여하는 슬롯까지 포함한 전체 조립 순서예요.
Bare stack — model만 넘기면 메인 에이전트는 보통 다음을 포함해요:
FilesystemMiddlewareSubAgentMiddleware(general-purpose 서브에이전트가 자동 추가되므로)SummarizationMiddlewarePatchToolCallsMiddleware- 프롬프트 캐싱 미들웨어(항상 등록, 지원하지 않는 모델에선 각 항목이 no-op)
- Harness profile extras와 제외 툴 필터링(해석된 모델 프로파일이 정의한 경우)
Full stack — 처음부터 끝까지:
SkillsMiddleware:skills를 넘길 때만. 파일 툴이 실행되기 전에 skill 메타데이터를 쓰도록 파일시스템 미들웨어 앞에 주입.FilesystemMiddleware: 파일 읽기·쓰기·디렉토리 탐색 같은 파일시스템 작업 처리.permissions를 넘기면 파일시스템 권한 강제가 여기 포함돼, 에이전트가 호출할 수 있는 모든 툴을 평가.SubAgentMiddleware: sync 서브에이전트가 최소 하나 있을 때만. 위임 작업을 위해 서브에이전트를 생성·조정.SummarizationMiddleware: 대화가 길어지면 컨텍스트 한도 안에 들도록 메시지 히스토리 요약(create_summarization_middleware 경유).PatchToolCallsMiddleware: 인터럽트 후 재개나 잘못된 툴 콜 인자를 받았을 때 메시지 히스토리의 매달린 툴 콜을 복구. Anthropic 프롬프트 캐싱과 아래 tail 스택보다 앞에 실행.AsyncSubAgentMiddleware: async 서브에이전트를 구성할 때만.- 우리의
middleware인자: Patch 뒤, 나머지 스택 앞에 병합..name이 위 내장 항목과 일치하는 인스턴스는 중복 대신 그 자리를 교체하고, 나머지는 여기에. - Harness profile extras: 해석된 모델 프로파일의 프로바이더 특화 미들웨어.
- 제외 툴 필터링: 프로파일이 제외 툴을 나열하면 그 툴을 에이전트에서 제거.
- 프롬프트 캐싱(
AnthropicPromptCachingMiddleware,BedrockPromptCachingMiddleware): 둘 다 항상 등록되고 Patch와 우리 미들웨어 뒤에 실행되어, 캐시된 접두사가 모델에 실제 전송되는 것과 일치하게 함. 지원하지 않는 모델에선 no-op(unsupported_model_behavior="ignore"). MemoryMiddleware:memory를 넘길 때만. 주입된 메모리 갱신이 캐시 접두사를 무효화하지 않도록 프로파일 extras와 프롬프트 캐싱 뒤에 배치.HumanInTheLoopMiddleware:interrupt_on을 넘길 때만. 설정된 툴 콜에서 인간 승인·입력을 위해 일시 중지.
내장 general-purpose 서브에이전트와 각 선언적 sync SubAgent 그래프는 create_deep_agent가 코드로 만드는 스택을 사용해요. 메인 에이전트와 대체로 유사하지만 두 가지가 달라요—이 내부 에이전트에서는 skills가 PatchToolCallsMiddleware 뒤에 실행되고(skills 설정 시 메인 에이전트에선 파일시스템 미들웨어 앞), 서브에이전트 그래프 안에는 SubAgentMiddleware가 없다(task 툴은 부모 에이전트만 노출).
LangChain은 재시도·폴백·PII 감지 같은 기능을 추가하는 prebuilt 미들웨어도 제공해요(Prebuilt middleware). deepagents 라이브러리는 고정 토큰 간격 대신 작업 사이 같은 적절한 시점에 요약을 트리거하는 create_summarization_tool_middleware도 노출해요(Summarization). 특정 LLM 프로바이더에 최적화된 미들웨어는 Middleware integrations 참고.
커스텀 미들웨어로 기능을 확장하거나 툴을 추가하거나 커스텀 훅을 구현할 수 있어요. @wrap_tool_call로 모든 툴 콜을 가로채 로깅하는 예시가 대표적이에요.
from langchain.agents.middleware import wrap_tool_call
from langchain.tools import tool
from deepagents import create_deep_agent
@tool
def get_weather(city: str) -> str:
"""Get the weather in a city."""
return f"The weather in {city} is sunny."
call_count = [0] # Use list to allow modification in nested function
@wrap_tool_call
def log_tool_calls(request, handler):
"""Intercept and log every tool call - demonstrates cross-cutting concern."""
call_count[0] += 1
tool_name = request.name if hasattr(request, "name") else str(request)
print(f"[Middleware] Tool call #{call_count[0]}: {tool_name}")
print(f"[Middleware] Arguments: {request.args if hasattr(request, 'args') else 'N/A'}")
# Execute the tool call
result = handler(request)
# Log the result
print(f"[Middleware] Tool call #{call_count[0]} completed")
return result
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
tools=[get_weather],
middleware=[log_tool_calls],
)
Subagents
세부 작업을 격리하고 컨텍스트가 부풀지 않게 하려면 서브에이전트를 사용해요.
import os
from typing import Literal
from deepagents import create_deep_agent
from tavily import TavilyClient
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(
query: str,
max_results: int = 5,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = False,
):
"""Run a web search"""
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
research_subagent = {
"name": "research-agent",
"description": "Used to research more in depth questions",
"system_prompt": "You are a great researcher",
"tools": [internet_search],
"model": "openai:gpt-5.5", # Optional override, defaults to main agent model
}
subagents = [research_subagent]
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
subagents=subagents,
)
자세한 내용은 Subagents 참고.
Backends
딥 에이전트의 툴은 가상 파일시스템을 사용해 파일을 저장·접근·편집할 수 있어요. 기본적으로 딥 에이전트는 StateBackend를 사용해요. skills나 memory를 쓰면 에이전트를 만들기 전에 기대하는 skill·memory 파일을 백엔드에 추가해야 해요.
- StateBackend (기본값):
langgraph상태에 저장되는 스레드 스코프 파일시스템 백엔드. 스레드 내에서는 턴 사이 파일이 유지되지만(체크포인터 경유) 스레드 간에는 공유되지 않아요.create_deep_agent(model=...)만 해도 내부적으로backend=StateBackend()이 적용돼요. - FilesystemBackend: 로컬 머신의 파일시스템. 에이전트에 직접 파일시스템 읽기·쓰기 접근을 주므로 주의해서 적절한 환경에서만 사용해야 해요. 안전하게는
CompositeBackend로 감싸서 내부 에이전트 데이터(오프로드된 툴 결과·대화 히스토리)가 프로젝트 파일 옆에 디스크로 쓰이지 않게 하세요.FilesystemBackend(root_dir=".", virtual_mode=True). - LocalShellBackend: 호스트에서 직접 셸 실행이 가능한 파일시스템. 파일시스템 툴과 명령 실행용
execute툴을 제공하지만, 호스트에서 제한 없는 셸·파일시스템 접근을 주므로 극히 주의해서 사용해야 해요.LocalShellBackend(root_dir=".", virtual_mode=True, env={"PATH": "/usr/bin:/bin"}). - StoreBackend: 스레드를 넘어 영속되는 장기 저장을 제공하는 파일시스템. namespace factory로 스코프.
- CompositeBackend: 백엔드를 섞는 방식. 특정 경로를 다른 라우트로.
- Sandbox backends: 격리된 컨테이너와
execute툴을 제공해 에이전트가 코드를 안전하게 실행할 수 있게 해요. sandboxes 참고.
FilesystemBackend·LocalShellBackend는 호스트에 직접 접근하므로 배포된 에이전트에서는 쓰면 안 돼요. 전체 백엔드 목록·커스텀 제작법은 backends 참고.
Sandboxes
에이전트가 파일 I/O 이상의 코드를 실행해야 한다면 격리된 컨테이너에 파일시스템과 execute 툴을 제공하는 샌드박스 백엔드를 사용해요. 예를 들어 Daytona를 쓰면 다음과 같아요.
uv add langchain-daytona
from daytona import Daytona
from deepagents import create_deep_agent
에이전트가 실행할 코드를 격리 환경에서 돌리고 싶다면 sandboxes 문서를 참고하세요.
Human-in-the-loop
일부 툴 작업은 민감해서 실행 전 인간 승인이 필요할 수 있어요. 툴마다 승인을 구성할 수 있어요.
from langchain.tools import tool
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
@tool
def remove_file(path: str) -> str:
"""Delete a file from the filesystem."""
return f"Deleted {path}"
@tool
def fetch_file(path: str) -> str:
"""Read a file from the filesystem."""
return f"Contents of {path}"
@tool
def notify_email(to: str, subject: str, body: str) -> str:
"""Send an email."""
return f"Sent email to {to}"
# Checkpointer is REQUIRED for human-in-the-loop
checkpointer = MemorySaver()
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
tools=[remove_file, fetch_file, notify_email],
interrupt_on={
"remove_file": True, # Default: approve, edit, reject, respond
"fetch_file": False, # No interrupts needed
"notify_email": {"allowed_decisions": ["approve", "reject"]}, # No editing
},
checkpointer=checkpointer, # Required!
)
에이전트·서브에이전트 모두 툴 콜 시, 그리고 툴 콜 내부에서도 인터럽트를 구성할 수 있어요. 자세한 내용은 Human-in-the-loop 참고.
Skills
skills로 딥 에이전트에 새 능력과 전문성을 줄 수 있어요. tools가 네이티브 파일시스템 동작 같은 저수준 기능을 다루는 반면, skills는 작업 완료 방법에 대한 상세 지시, 참조 정보, 템플릿 같은 자산을 담을 수 있어요. 이 파일들은 에이전트가 현재 프롬프트에 유용하다고 판단했을 때만 로드돼요—이 점진적 공개(progressive disclosure) 덕분에 시작 시 에이전트가 고려해야 할 토큰·컨텍스트가 줄어들어요. 예시는 Deep Agents example skills 참고.
create_deep_agent 인자로 skills=[...]를 넘겨 추가해요. StateBackend에서는 skill 파일을 virtual filesystem에 시드해야 하므로, urlopen으로 SKILL.md를 받아 create_file_data로 만들어 "files"에 넣고 invoke에 전달해요.
from urllib.request import urlopen
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from deepagents.backends.utils import create_file_data
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
backend = StateBackend()
skill_url = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/libs/code/examples/skills/langgraph-docs/SKILL.md"
with urlopen(skill_url) as response:
skill_content = response.read().decode('utf-8')
skills_files = {
"/skills/langgraph-docs/SKILL.md": create_file_data(skill_content),
}
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
backend=backend,
skills=["/skills/"],
checkpointer=checkpointer,
)
result = agent.invoke(
{
"messages": [{"role": "user", "content": "What is langgraph?"}],
# Seed the default StateBackend's in-state filesystem (virtual paths must start with "/").
"files": skills_files,
},
config={"configurable": {"thread_id": "12345"}},
)
StoreBackend에서는 backend.upload_files(...)로, FilesystemBackend에서는 역시 backend.upload_files(...)로 skill 파일을 올려요.
Memory
AGENTS.md 파일로 딥 에이전트에 추가 컨텍스트를 제공할 수 있어요. memory=["./AGENTS.md"]처럼 넘기면 시작 시 로드돼요.
Profiles
harness profile은 create_deep_agent가 일치하는 모델을 선택하면 자동 적용하는 모델별 설정의 재사용 가능한 번들예요. 호출 위치가 아니라 모델을 따라가는 동작을 원할 때 적합한데—Claude의 지시 스타일에 맞춘 시스템 프롬프트 접미사, GPT용으로 다시 쓴 툴 설명, 특정 프로바이더에서만 의미 있는 추가 미들웨어 같은 경우예요.
하나의 프로파일은 커스텀 기본 시스템 프롬프트(base_system_prompt), 이어붙이는 접미사(system_prompt_suffix), 툴 설명 오버라이드, 제외할 툴·미들웨어, 주입할 추가 미들웨어, 자동 추가되는 general-purpose 서브에이전트 편집을 담을 수 있어요.
from deepagents import HarnessProfile, register_harness_profile
# Append a system-prompt suffix whenever gpt-5.5 is selected.
register_harness_profile(
"openai:gpt-5.5",
HarnessProfile(system_prompt_suffix="Respond in under 100 words."),
)
등록 키·병합 의미론·플러그인 패키징은 Profiles, 모델 구성 인자(API 키·타임아웃·재시도 설정)를 패키징하는 보다 좁은 provider profiles도 참고하세요.
Structured output
Deep Agents는 structured output을 지원해요. create_deep_agent() 호출의 response_format 인자로 원하는 구조화 출력 스키마를 넘기면, 모델이 구조화 데이터를 생성할 때 캡처·검증되어 딥 에이전트 상태의 'structured_response' 키로 반환돼요.
import os
from typing import Literal
from pydantic import BaseModel, Field
from tavily import TavilyClient
from deepagents import create_deep_agent
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(
query: str,
max_results: int = 5,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = False,
):
"""Run a web search"""
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
class WeatherReport(BaseModel):
"""A structured weather report with current conditions and forecast."""
location: str = Field(description="The location for this weather report")
temperature: float = Field(description="Current temperature in Celsius")
condition: str = Field(
description="Current weather condition (e.g., sunny, cloudy, rainy)"
)
humidity: int = Field(description="Humidity percentage")
wind_speed: float = Field(description="Wind speed in km/h")
forecast: str = Field(description="Brief forecast for the next 24 hours")
agent = create_deep_agent(
model=model,
response_format=WeatherReport,
tools=[internet_search],
)
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "What's the weather like in San Francisco?",
}
]
}
)
print(result["structured_response"])
# location='San Francisco, California' temperature=18.3 condition='Sunny' humidity=48 wind_speed=7.6 forecast='Pleasant sunny conditions expected to continue with temperatures around 64°F (18°C) during the day, dropping to around 52°F (11°C) at night. Clear skies with minimal precipitation expected.'
자세한 내용과 예시는 response format 참고.
Advanced
create_deep_agent은 create_agent 위에 미들웨어 스택을 미리 조립해요. 정확히 어떤 기능을 포함할지 고르며 완전히 커스텀 에이전트를 만들고 싶다면 Configure the harness 참고.