딥 에이전트를 프로덕션으로

딥 에이전트를 프로덕션으로 (Going to production)

로컬에서 잘 돌아가던 딥 에이전트를 실제 서비스로 올릴 때는 로컬과는 다른 고민이 필요해요. 이 가이드는 로컬 프로토타입에서 프로덕션 배포로 넘어가는 과정을 다루는데, 메모리 스코프를 어떻게 잡을지, 실행 환경을 어떻게 구성할지, 가드레일을 어떻게 추가할지, 프론트엔드를 어떻게 연결할지 순서대로 설명해 줘요. 프로덕션에서 정보가 어떻게 공유·접근되는지를 정하는 기본 요소(Thread, User, Assistant)부터 이해하면 좋아요.

출처: 공식문서

개요

에이전트는 메모리와 실행 환경에서 얻은 정보로 작업을 수행해요. 프로덕션에서는 정보가 어떻게 공유되고 접근되는지를 결정하는 기본 개념이 몇 가지 있어요.

  • Thread: 하나의 대화. 메시지 히스토리와 스크래치 파일은 기본적으로 스레드에 스코프되어 다른 곳으로 넘어가지 않아요.
  • User: 에이전트와 상호작용하는 사람. 메모리와 파일은 특정 사용자에게 비공개이거나 사용자 간에 공유될 수 있어요. 정체성과 인가는 auth 레이어에서 제공돼요.
  • Assistant: 설정된 에이전트 인스턴스. 메모리와 파일은 하나의 어시스턴트에 묶이거나 전체에서 공유될 수 있어요.

이 페이지는 LangSmith Deployments, 프로덕션 고려사항, 메모리, 실행 환경, 가드레일, 프론트엔드를 다룹니다.

LangSmith Deployments

딥 에이전트를 프로덕션으로 올리는 권장 경로는 Managed Deep Agents예요. CLI 중심의 호스팅 런타임으로, LangSmith에서 딥 에이전트를 만들고 실행·운영할 수 있어요. 현재 비공개 프리뷰 상태이고(웨이트리스트), 커스텀 애플리케이션 코드·라우트·고급 인증이 필요한 팀은 LangSmith Deployment를 직접 구성할 수 있어요. 어느 경로든 threads, runs, store, checkpointer 같은 인프라를 프로비저닝해 주므로 직접 세팅할 필요가 없어요. 전통적인 LangSmith Deployment는 인증, webhooks, cron jobs, observability를 기본 제공하고, 에이전트를 MCPA2A로 노출할 수도 있어요.

이 페이지의 모든 코드 스니펫은 특별히 명시하지 않는 한 다음 langgraph.json을 사용해요.

{
  "dependencies": ["."],
  "graphs": {
    "agent": "./agent.py:agent"
  },
  "env": ".env"
}

langgraph.json은 LangGraph 플랫폼이 애플리케이션을 어떻게 빌드·실행할지 알려주는 설정 파일이에요. 프로젝트 루트에 있으며, 로컬 개발(langgraph dev)과 프로덕션 배포 모두에 필요해요. 핵심 필드는 다음과 같아요.

필드 설명
dependencies 설치할 패키지. ["."]은 현재 디렉토리를 패키지로 설치(requirements.txt, pyproject.toml, package.json에서 읽음).
graphs 그래프 ID를 코드 위치에 매핑. 각 항목은 "<id>": "./<file>:<variable>" 형식 — <id>는 API로 그래프를 호출할 때 쓰는 이름, <variable><file>에서 export한 컴파일된 그래프나 생성자 함수.
env 환경 변수(API 키, 시크릿)가 담긴 .env 파일 경로. 빌드 시점에 설정되어 런타임에 사용 가능.

전체 설정 옵션(커스텀 Docker 스텝, store 인덱싱, auth 핸들러 등)은 application structure를 참고하세요.

프로덕션 고려사항

에이전트 호출

프로덕션에서는 모든 호출에 두 가지 런-레벨 파라미터를 함께 보내는 것이 좋아요.

  • thread_id (config={"configurable": {"thread_id": ...}}로 전달): 대화의 안정적 식별자. checkpointer가 이걸로 메시지 히스토리를 저장·재개하므로, 후속 턴이 같은 대화를 이어가요. 새 대화를 시작하려면 새 thread_id를 생성하면 돼요.
  • context: 호출 시점에 툴·미들웨어가 읽는 런당 데이터. 예: user_id, API 키, 피처 플래그, 세션 메타데이터. context_schema로 형태를 정의하고 runtime.context로 접근해요. Runtime context 참고.

이 둘은 독립적이고 거의 항상 함께 전달돼요.

from dataclasses import dataclass

from deepagents import create_deep_agent
from langchain_core.utils.uuid import uuid7


@dataclass
class Context:
    user_id: str


agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    context_schema=Context,
)

# Start a conversation
config = {"configurable": {"thread_id": str(uuid7())}}
agent.invoke(
    {"messages": [{"role": "user", "content": "Plan a 3-day trip to Tokyo"}]},
    config=config,
    context=Context(user_id="user-123"),
)

# Follow-up on the same conversation: reuse the same thread_id
agent.invoke(
    {"messages": [{"role": "user", "content": "Make it 5 days instead"}]},
    config=config,
    context=Context(user_id="user-123"),
)

프로바이더가 달라지면 model 문자열만 바꾸면 돼요 — 예를 들어 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 같은 식이에요. 코드 구조는 동일해요.

LangGraph SDK로 배포할 때는 SDK가 스레드를 관리하고 반환된 thread_id를 각 실행에 넘겨요.

from langgraph_sdk import get_client

client = get_client(url="<DEPLOYMENT_URL>", api_key="<LANGSMITH_API_KEY>")

thread = await client.threads.create()
async for chunk in client.runs.stream(
    thread["thread_id"],  # [!code highlight]
    "agent",
    input={"messages": [{"role": "user", "content": "Plan a 3-day trip to Tokyo"}]},
    context={"user_id": "user-123"},  # [!code highlight]
    stream_mode="updates",
):
    print(chunk.data)

thread_id대화(메시지 히스토리, 체크포인트)를 스코프하고, context는 툴·미들웨어가 읽는 런당 데이터를 담아요. 둘은 독립적이라 하나를 바꿔도 다른 하나에는 영향이 없고, 둘 중 하나만 전달하거나 둘 다 전달할 수 있어요.

멀티 테넌시

에이전트가 여러 사용자를 대상으로 하면 세 가지를 처리해야 해요—각 사용자가 누구인지 검증하고, 무엇에 접근할 수 있는지 통제하고, 사용자를 대신해 행동할 때 사용할 자격증명을 관리하는 것이죠.

사용자 정체성과 접근 제어

LangSmith Deployments는 사용자 정체성을 세우는 커스텀 인증과, 스레드·어시스턴트·store 네임스페이스 같은 리소스 접근을 통제하는 authorization handlers를 지원해요. 인증 후 실행되는 핸들러는 다음을 할 수 있어요.

  • 리소스에 소유권 메타데이터 태깅(예: owner: user_id)
  • 사용자가 자기 리소스만 보도록 필터 반환
  • 권한 없는 작업에 HTTP 403으로 접근 거부

단계별 튜토리얼은 Make conversations private, 워크스루는 custom auth 비디오를 참고하세요.

팀 접근 제어 (RBAC)

LangSmith의 role-based access control은 팀의 누가 에이전트를 배포·구성·모니터링할 수 있는지 결정해요. 아래 표는 기본 역할이에요.

역할 접근
Workspace Admin 설정·멤버 관리 포함 전체 권한
Workspace Editor 리소스 생성·수정, 단 runs 삭제·멤버 관리는 불가
Workspace Viewer 읽기 전용 접근

세분화된 권한의 커스텀 역할은 Enterprise 플랜에서 사용 가능해요. 전체 권한 모델은 RBAC 참조 참고.

엔드유저 자격증명

에이전트가 사용자를 대신해 외부 API를 호출해야 할 때(예: GitHub 저장소 읽기, Slack 메시지 보내기, 데이터 웨어하우스 조회) 자격증명을 하드코딩하지 않고 전달하는 방법이 필요해요.

OAuth via Agent Auth. Agent Auth는 관리형 OAuth 2.0 흐름을 제공해요. OAuth 프로바이더를 구성하면 에이전트가 사용자별로 스코프된 토큰을 요청할 수 있어요. 처음 사용 시 에이전트가 인터럽트로 실행을 멈추고 OAuth 동의 URL을 보여주며, 사용자가 인증하면 유효한 토큰으로 재개돼요. 토큰은 자동 저장·갱신돼요.

from langchain_auth import Client
from langchain.tools import tool, ToolRuntime

auth_client = Client()

# Inside your agent's tool:
@tool
async def github_action(runtime: ToolRuntime):
    """Perform an action on behalf of the user via GitHub."""
    auth_result = await auth_client.authenticate(
        provider="github",
        scopes=["repo", "read:org"],
        user_id=runtime.server_info.user.identity,  # [!code highlight]
    )
    # Use auth_result.token for GitHub API calls on the user's behalf

샌드박스를 위한 자격증명 주입. 에이전트가 외부 API를 호출하는 코드를 샌드박스 안에서 실행한다면, sandbox auth proxy가 아웃바운드 요청에 자격증명을 자동 주입해서 샌드박스 코드가 raw API 키를 받지 않게 해줘요.

워크스페이스 시크릿. 모든 사용자가 공유하는 API 키(예: 조직의 LLM 프로바이더 키, 검색 API 키)는 LangSmith의 workspace secrets로 저장하세요.

Async

LLM 기반 애플리케이션은 강한 I/O 바운드예요—언어 모델, 데이터베이스, 외부 서비스 호출을 하거든요. async 프로그래밍은 이 작업들을 블로킹 대신 동시에 실행하게 해서 처리량과 응답성을 높여요.

LangChain은 async 메서드 이름에 a 접두사를 붙이는 관례를 써요(예: ainvoke, abefore_agent, astream). sync·async 변형은 같은 클래스/네임스페이스에 있어요.

프로덕션을 만들 때:

  • async 툴을 만들어요. LangChain은 블로킹을 막기 위해 sync 툴을 별도 스레드에서 실행하지만, 네이티브 async는 스레딩 오버헤드를 완전히 없애요.
  • async 미들웨어 메서드를 사용해요. 커스텀 미들웨어는 async 훅을 구현해야 해요(예: before_agent 대신 abefore_agent).
  • 외부 리소스 수명주기에 async 사용. 샌드박스를 만들거나 MCP 서버에 연결하는 것은 네트워크 호출이라 await해야 해요. 그래서 이런 리소스를 프로비저닝하는 graph factories는 async예요.

Durability

딥 에이전트는 LangGraph 위에서 돌며 기본으로 영속 실행(durable execution)을 제공해요. persistence 레이어가 매 스텝 상태를 체크포인트하므로, 실패·타임아웃·human-in-the-loop 일시 중지로 중단된 실행은 마지막 기록 상태부터 재개되고 이전 스텝을 다시 처리하지 않아요. 많은 서브에이전트를 띄우는 장시간 딥 에이전트라도 중간 실패가 완료된 작업을 잃게 하지 않아요.

체크포인팅은 다음도 가능하게 해줘요.

  • 무기한 인터럽트. Human-in-the-loop 워크플로는 분·일 단위로 멈췄다가 정확히 그 지점에서 재개할 수 있어요.
  • 타임 트래블. 체크포인트된 모든 스텝은 되감을 수 있는 스냅샷이라, 문제가 생기면 이전 상태에서 재생할 수 있어요.
  • 민감 작업의 안전한 처리. 결제나 되돌릴 수 없는 작업에서는 체크포인트가 감사 흔적이자, 액션을 유발한 정확한 상태를 검사할 수 있는 복구 지점이 돼요.

메모리

메모리가 없으면 모든 대화는 처음부터 시작돼요. 메모리는 에이전트가 대화 간 정보(사용자 선호, 학습한 지시, 과거 경험)를 유지해서 시간이 지나며 동작을 개인화하게 해줘요. 메모리 유형 개요는 memory concepts 가이드 참고.

스코핑

메모리는 항상 대화 간 영속돼요. 핵심 질문은 사용자·어시스턴트 경계에서 어떻게 스코프하느냐예요. 누가 데이터를 보고 수정해야 하는지에 따라 올바른 스코프가 달라져요.

스코프 네임스페이스 사용 사례 예시
User (권장 기본값) (user_id) 사용자별 선호·컨텍스트 "간결하게 답해 줘"
Assistant (assistant_id) 한 어시스턴트의 공유 지시 "게시물은 280자로 제한"
Global (org_id) 모든 사용자·어시스턴트의 읽기 전용 정책 "내부 가격 공개 금지"

공유 메모리(어시스턴트·사용자·조직 스코프)는 프롬프트 인젝션의 통로가 될 수 있어요. 사용자가 다른 사용자의 대화가 읽는 메모리에 쓸 수 있다면, 악의적 사용자가 그 공유 상태에 지시를 주입할 수 있어요. 적절한 곳에서는 읽기 전용 접근을 강제하세요. 예를 들어 조직 정책은 에이전트가 아니라 애플리케이션 코드로만 쓸 수 있게 하고, permissions으로 공유 경로에 대한 쓰기를 선언적으로 거부하거나, backend policy hooks로 커스텀 검증 로직을 쓰는 방법이 있어요.

구성

Deep Agents에서 메모리는 가상 파일시스템의 파일로 저장돼요. 기본적으로 파일은 단일 스레드(대화)에 스코프되고 스레드 간 공유되지 않아요. 스레드를 넘어 메모리를 공유하려면 /memories/ 같은 경로를 LangGraph Store에 쓰는 StoreBackend로 라우트해요. CompositeBackend를 쓰면 스레드 스코프 스크래치 공간과 스레드 간 장기 메모리를 모두 줄 수 있어요.

아래의 rt.server_info·rt.execution_info 네임스페이스 패턴은 deepagents>=0.5.0이 필요해요.

구성은 네임스페이스 팩토리로 스코프를 조절해요. 대표적으로 사용자 스코프(권장) 예시는 다음과 같아요.

from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                namespace=lambda rt: (
                    rt.server_info.assistant_id,  # [!code highlight]
                    rt.server_info.user.identity,  # [!code highlight]
                ),
            ),
        },
    ),
    system_prompt="""You have persistent memory at /memories/.

    Read /memories/instructions.txt at the start of each conversation for
    accumulated knowledge and preferences. When you learn something that
    should persist, update that file.""",
)

네임스페이스를 assistant_id로 잡으면 같은 어시스턴트의 모든 사용자가 메모리를 공유하고, user_id만으로 잡으면 사용자가 어느 어시스턴트를 쓰든 따라다니는 전역 사용자 프로필이 되고, org_id로 잡으면 조직 전체 정책이 돼요. 애플리케이션 코드에서도 Store API로 store를 읽고 쓸 수 있어요. 전체 네임스페이스 팩토리 API는 namespace factories, 자가 개선 지시·지식베이스 같은 메모리 패턴은 long-term memory 참고.

실행 환경

로컬에서는 에이전트가 디스크의 파일을 읽고 쓰고 셸 명령을 바로 실행할 수 있어요. 프로덕션에서는 격리와 영속성을 고려해야 해요. 올바른 세팅은 에이전트가 코드를 실행할 필요가 있느냐에 달려 있어요.

  • 파일시스템 백엔드는 에이전트가 파일만 읽고 쓴다면 충분해요. 영속성 요구에 맞는 백엔드를 고르세요—스레드 스코프 스크래치, 스레드 간 저장, 둘의 혼합.
  • 샌드박스는 셸 명령 실행용 execute 툴이 있는 격리된 컨테이너를 추가해요. 코드 실행·패키지 설치·파일 I/O 이상의 작업이 필요하면 샌드박스를 쓰세요.

파일시스템

무엇이 영속돼야 하느냐에 따라 백엔드를 고르세요.

  • StateBackend (기본값): 스레드 스코프 스크래치 공간. 스레드 내에서는 체크포인터로 턴 사이 파일이 유지되지만 스레드 간엔 공유되지 않아요. 매 스텝 체크포인트되므로 큰 파일은 쓰지 않는 게 좋아요.
  • StoreBackend: 대화 간에도 살아남는 스레드 간 저장. namespace factory로 스코프.
  • CompositeBackend: 둘을 혼합. 기본은 스레드 스코프 스크래치 + /memories/ 같은 특정 경로는 스레드 간 라우트.
  • ContextHubBackend: LangSmith Hub 저장소(owner/name 또는 name)의 영속 파일. 별도 LangGraph store를 프로비저닝하지 않고 LangSmith 네이티브 영속을 원할 때 사용.

전체 백엔드 목록과 커스텀 제작법은 backends 참고.

FilesystemBackendLocalShellBackend는 호스트에 직접 접근해요. 배포된 에이전트에서는 사용하지 마세요.

샌드박스

에이전트가 코드를 실행해야 한다면(파일을 읽고 쓰는 것 이상) 샌드박스를 사용하세요. 샌드박스는 파일시스템과 셸 명령 실행용 execute 툴을 모두 제공하고, 전부 격리된 컨테이너 안에서 동작해요. 이 격리는 호스트도 보호해요—에이전트 코드가 메모리를 소진하거나 크래시해도 샌드박스만 영향받고 서버는 계속 실행돼요.

수명주기

핵심 결정은 샌드박스가 얼마나 오래 사느냐예요. 대화마다 새로 만들까, 아니면 대화가 영속 환경을 공유할까요?

스코프 샌드박스 ID 저장 위치 수명주기 예시 사용 사례
Thread-scoped Thread 메타데이터 대화마다 새로, TTL에 정리 각 대화를 깨끗하게 시작하는 데이터 분석 봇
Assistant-scoped Assistant 설정 모든 대화가 공유 대화 간 클론된 저장소를 유지하는 코딩 어시스턴트

아래 예시는 정적 그래프 대신 async graph factory를 사용해요. 샌드박스가 올바른 샌드박스를 조회·생성하려면 thread_idassistant_id가 필요하기 때문이에요. 그래프 팩토리는 전체 Runtime(server_info·execution_info 없음)을 받지 않고, 대신 RunnableConfig를 받아 config["configurable"]에서 thread_id·assistant_id를 읽어요. 샌드박스 생성은 I/O 바운드 작업이라 팩토리는 async예요.

Thread-scoped (가장 흔함) — 각 대화가 자기 샌드박스를 가져요. graph factory가 런 설정에서 thread_id를 읽으므로 스레드마다 자동으로 격리된 환경을 받아요. 샌드박스 TTL이 만료되면 정리돼요.

from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()


async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  # [!code highlight]
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="google_genai:gemini-3.6-flash",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )

Assistant-scoped — 모든 대화가 샌드박스 하나를 공유해요. graph factoryconfig["configurable"]에서 assistant ID를 읽으므로, 같은 어시스턴트의 모든 스레드가 같은 환경으로 돌아가요. 파일·설치 패키지·클론한 저장소가 대화 간 유지돼요.

from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()


async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  # [!code highlight]
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="google_genai:gemini-3.6-flash",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )

Assistant 스코프 샌드박스는 시간이 지나며 파일·설치 패키지·기타 상태가 쌓여요. 샌드박스 프로바이더에서 TTL을 설정하거나, 스냅샷으로 주기 리셋하거나, 정리 로직을 구현해서 디스크·메모리가 무한정 커지지 않게 하세요.

agent 변수는 async 함수(컴파일된 그래프가 아님)이므로 서버가 이를 graph factory로 취급하고 실행마다 호출하면서 config를 주입해요. 팩토리는 이름으로 샌드박스를 조회·생성하고, 그 샌드박스에 연결된 새 에이전트 그래프를 반환해요.

langgraph deploy로 배포한 뒤에는 SDK로 애플리케이션 코드에서 에이전트를 호출해요. 클라이언트 코드는 스코프와 관계없이 동일해요—스코핑은 전부 위 에이전트 팩토리에서 처리되지만 동작은 달라요. 스레드 스코프에선 각 스레드가 자기 샌드박스를 갖고, 같은 스레드의 후속 메시지는 같은 샌드박스를 재사용하며 새 스레드는 항상 깨끗하게 시작돼요. 어시스턴트 스코프에선 모든 스레드가 샌드박스 하나를 공유해서, 클론한 저장소·설치 의존성·빌드 산출물 같은 재생성 비용이 큰 상태가 있으면 유용해요.

파일 전송

샌드박스는 격리된 컨테이너라 애플리케이션 코드가 안쪽 파일에 직접 접근할 수 없어요. upload_files()download_files()로 경계를 넘어 데이터를 옮겨요.

  • 에이전트 실행 전 샌드박스 시딩: 사용자 파일, skill 스크립트, 설정, 영속 메모리를 업로드해서 에이전트가 처음부터 필요한 것을 갖게 해요.
  • 에이전트 완료 후 결과 회수: 생성된 산출물(리포트·플롯·익스포트)을 다운로드하고, 갱신된 메모리를 스레드 간에 동기화해요.

프로바이더별 파일 전송 예시는 working with files, 프로바이더 설정·보안·수명주기 패턴은 전체 sandboxes 가이드를 참고하세요. skill·메모리를 커스텀 미들웨어(abefore_agent/aafter_agent 훅)로 동기화하는 예시도 원문에 있으니 참고할 수 있어요.

시크릿 관리

샌드박스는 격리된 컨테이너라 호스트의 환경 변수를 사용할 수 없어요. 샌드박스 코드에 API 키·시크릿을 제공하는 방법은 두 가지예요.

Auth proxy (권장). sandbox auth proxy가 샌드박스의 아웃바운드 요청을 가로채 목적지 호스트 기반으로 인증 헤더를 자동 주입해요. 샌드박스 코드는 평범하게 외부 API를 호출하고, 프록시가 올바른 자격증명을 추가해요. 그래서 API 키가 샌드박스 코드·환경 변수·로그에 절대 나타나지 않아요.

{
  "proxy_config": {
    "rules": [
      {
        "name": "openai-api",
        "match_hosts": ["api.openai.com"],
        "inject_headers": {
          "Authorization": "Bearer ${OPENAI_API_KEY}"
        }
      },
      {
        "name": "anthropic-api",
        "match_hosts": ["api.anthropic.com"],
        "inject_headers": {
          "x-api-key": "${ANTHROPIC_API_KEY}"
        }
      }
    ]
  }
}

${SECRET_KEY} 참조는 LangSmith workspace settings에 저장된 시크릿에 대해 해석돼요. 참조하는 템플릿을 만들기 전에 먼저 시크릿을 구성하세요.

워크스페이스 시크릿. 프록시 주입이 필요 없는 API 키(예: 샌드박스 코드가 아니라 에이전트 서버 자체가 쓰는 키)는 LangSmith workspace secrets로 저장해요. 워크스페이스의 모든 에이전트에 런타임 환경 변수로 제공돼요.

샌드박스에 시크릿을 환경 변수나 파일 업로드로 넘기는 것은 피하세요. 에이전트는 샌드박스 안의 접근 가능한 파일·환경 변수를 다 읽을 수 있는데, 자격증명도 포함돼요. auth proxy는 시크릿이 샌드박스에 아예 들어가지 않게 해요.

가드레일

프로덕션의 에이전트는 자율 실행되므로 무한 루프에 빠지거나, 레이트 리밋에 걸리거나, 민감 정보가 담긴 사용자 데이터를 처리할 수 있어요. Deep Agents는 두 가지 보호 레이어를 제공해요.

  • Permissions: 에이전트가 읽고 쓸 파일·디렉토리를 통제하는 선언적 allow/deny 규칙.
  • Fault tolerance: 레이트 리밋, 재시도, 폴백, 에러 처리.
  • Data privacy: PII가 모델에 도달하거나 로그에 저장되기 전에 감지·처리하는 미들웨어.

Permissions

Permissions은 에이전트가 읽고 쓸 파일·디렉토리를 통제하는 선언적 allow/deny 규칙이에요. 에이전트를 작업 디렉토리로 격리하거나, 민감 파일을 보호하거나, 읽기 전용 메모리를 강제하는 데 써요. 규칙은 선언 순서대로 평가되고 첫 번째 매칭 규칙이 승리해요.

Fault tolerance

레이트 리밋, 재시도, 폴백, 에러 처리는 Fault tolerance 문서를 참고하세요.

Data privacy

에이전트가 이메일·신용카드 번호·기타 PII를 담을 수 있는 사용자 입력을 처리한다면, 모델에 도달하거나 로그에 저장되기 전에 감지·처리할 수 있어요.

from deepagents import create_deep_agent
from langchain.agents.middleware import PIIMiddleware

agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    middleware=[
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
    ],
)

전략에는 redact([REDACTED_EMAIL]로 교체), mask(부분 마스킹, ****-****-****-1234), hash(결정적 해시), block(에러 발생)이 있어요. 도메인 특화 패턴을 위한 커스텀 디텍터도 작성할 수 있어요. 전체 설정은 PIIMiddleware 참고.

기본 Deep Agents 미들웨어 스택은 Customization, 추가 LangChain 사전 빌드 미들웨어(재시도·폴백·PII 감지 등)는 Prebuilt middleware 참고.

프론트엔드

Deep Agents는 useStream으로 UI를 에이전트 백엔드에 연결해요. useStream은 React·Vue·Svelte·Angular에서 사용 가능한 프론트엔드 훅으로, 메시지·서브에이전트 진행·커스텀 상태를 실시간 스트리밍해요.

로컬에서는 useStreamhttp://localhost:2024를 가리키지만, 프로덕션에서는 LangSmith Deployment를 가리키고 연결이 끊겨도 진행 상황을 잃지 않도록 재연결을 구성하세요.

import { useStream } from "@langchain/react";

function App() {
  const stream = useStream<typeof agent>({
    apiUrl: "https://your-deployment.langsmith.dev",
    assistantId: "agent",
  });
}

많은 서브에이전트를 띄우는 딥 에이전트 워크플로에서는 제출 시 높은 recursionLimit을 설정해서 장시간 실행이 중간에 잘리지 않게 하세요.

stream.submit(
  { messages: [{ type: "human", content: text }] },
  {
    streamSubgraphs: true,
    config: { recursionLimit: 10000 },
  },
);

서브에이전트 카드·할 일 목록·커스텀 상태 렌더링 같은 딥 에이전트 전용 UI 패턴은 frontend 가이드를 참고하세요.

더 알아보기 (Learn more)