
Deep Agents의 인터프리터: 도구 호출과 샌드박스 사이의 코드 실행
요약
Deep Agents는 도구 호출(tool calls)과 완전한 샌드박스(sandboxes) 사이의 중간 단계인 '인터프리터'를 도입하여 에이전트의 코드 실행 능력을 강화합니다. 인터프리터는 에이전트 루프 내의 임베디드 런타임으로서, 모델 컨텍스트 외부에서 실시간 작업 값을 유지하며 다단계 로직을 효율적으로 처리합니다.
핵심 포인트
- 인터프리터는 도구 호출과 샌드박스 사이의 중간 지점으로, 범위가 지정된 기능(scoped capabilities)을 제공합니다.
- 인터프리터 상태는 메시지 기록이나 파일 시스템과 별개로 실시간 작업 값을 유지하는 세 번째 컨텍스트 표면 역할을 합니다.
- 프로그래밍 방식의 도구 호출을 통해 특정 작업에서 토큰 사용량을 최대 35%까지 절감할 수 있습니다.
- 에이전트는 인터프리터를 통해 변수 정의, 값 검사, 헬퍼 함수 작성 등 복잡한 다단계 로직을 수행할 수 있습니다.
핵심 요약 (Key Takeaways)
인터프리터 (Interpreters)는 순차적인 도구 호출 (tool calls)과 완전한 샌드박스 (sandboxes) 사이에 위치합니다. 에이전트는 전체 환경을 상속받지 않고도 범위가 지정된 기능 (scoped capabilities) 이상의 코드 수준의 조합 (composition) 능력을 갖게 됩니다.
인터프리터 상태 (Interpreter state)는 세 번째 컨텍스트 표면 (context surface)입니다. 메시지 기록 (Message history)은 모델이 현재 추론하는 내용을 위한 것이고, 파일 시스템 (filesystem)은 영구적인 산출물 (durable artifacts)을 위한 것이라면, 인터프리터 상태는 아직 모델의 입력으로 들어갈 필요가 없는 실시간 작업 값 (live working values)을 위한 것입니다.
프로그래밍 방식의 도구 호출 (Programmatic tool calling)이 미들웨어 (middleware)로 포함됩니다. 화이트리스트에 등록된 도구들은 인터프리터 내부의 tools 네임스페이스 (namespace) 아래에 나타나며, 어떤 모델과도 함께 작동하고 초기 테스트 결과 일부 작업에서 토큰 (tokens) 사용량을 최대 35%까지 줄였습니다.
요약 (TL;DR): 우리는 Deep Agents에 인터프리터를 추가하고 있습니다. 이는 에이전트 루프 (agent loop) 내부에서 에이전트가 코드를 작성하고 실행할 수 있는 작은 임베디드 런타임 (embedded runtimes)입니다. 인터프리터는 에이전트에게 일회성 도구 호출과 완전한 샌드박스 사이의 중간 지점을 제공하여, 에이전트가 다단계 작업을 표현하고, 중간 상태를 모델 컨텍스트 (model context) 외부로 유지하며, 코드와 작업을 더 예측 가능한 방식으로 실행할 수 있게 합니다.
인터프리터란 무엇인가?
인터프리터는 에이전트가 작업하는 동안 코드를 작성할 수 있는 작은 임베디드 런타임 (embedded runtime)입니다. 기능적으로는 에이전트에게 Python 또는 Node REPL을 제공하는 것과 같습니다. 에이전트는 변수를 정의하고, 값을 검사하며, 헬퍼 함수 (helper functions)를 작성하고, 호출 간에 상태 (state)를 재사용할 수 있습니다.
오늘날 많은 에이전트들은 이미 호스트 (host) 또는 샌드박스 (sandbox) 환경에 명령을 내림으로써 코드를 실행하고 있습니다. 이는 명령 실행, 종속성 설치 (installing dependencies), 또는 파일 시스템 (filesystem) 조작과 같이 환경 수준의 작업이 필요할 때 매우 유용합니다. 인터프리터는 다른 계층을 목표로 합니다. 에이전트는 위임 (delegation)을 조정하고, 도구 호출 (tool calls)을 조합하며, 구조화된 데이터 (structured data)를 변환하고, 어떤 정보가 모델로 돌아가야 할지를 결정하기 위해 에이전트 루프 (agent loop) 내부에서 실행되는 코드를 작성합니다.
// 에이전트가 다음과 같이 코드를 작성합니다
const rows = [
{ team: "support", tickets: 18 },
...
이는 에이전트에게 도구 호출 (tool calls)의 연속으로 깔끔하게 떨어지지 않는 동작을 표현할 수 있는 새로운 공간을 제공합니다. 에이전트는 다단계 로직 (multi-step logic)을 위한 작업 공간을 얻게 되며, 동시에 하네스 (harness)는 해당 작업 공간이 무엇을 건드릴 수 있는지 여전히 제어합니다. 인터프리터 (interpreter)는 임시 상태 (temporary state)를 유지할 수 있으며, 중요한 부분만을 반환할 수 있습니다.
인터프리터의 위치
에이전트를 생각할 때, 보통 도구를 부착하는 것을 떠올립니다.
가장 단순한 형태의 에이전트에서, 에이전트는 루프 (loop) 내에서 해당 도구들을 사용합니다. 즉, 모델이 하나의 도구를 호출하고, 관찰 결과 (observation)를 검토한 다음, 다음에 무엇을 할지 결정합니다. 이러한 한 단계씩 진행하는 방식은 디버깅과 평가가 직관적이며, 많은 워크플로 (workflows)가 즉각적인 관찰 결과에 대해 추론하는 방식을 필요로 합니다.
샌드박스 (Sandboxes)는 에이전트에게 환경 내에서 명령을 실행하고, 의존성 (dependencies)을 설치하며, 파일과 작업할 수 있는 bash 도구를 제공함으로써 이를 확장합니다.
하지만 두 방식 모두 단점이 있습니다. 샌드박스는 (코드를 직접 작성하여 수행할 수 있으므로) 로컬 절차를 처리할 수 있지만, 프로비저닝 (provisioning)과 확장 (scale)이 더 어려울 수 있습니다. 또한, 순수하게 직렬적인 도구 루프 (serial tool loops)는 중간 단계들이 주로 다음 단계의 입력값으로 들어가는 경우에 다소 어색할 수 있습니다.
일부 에이전트 작업은 이 두 극단 사이의 영역에 위치하며, 인터프리터는 이 사이에 아주 적절하게 끼어 들어갑니다. 인터프리터는 에이전트에게 전체 환경을 부여하지 않으면서도, 범위가 지정된 기능 (scoped capabilities)에 대해 코드 수준의 구성 (code-level composition)을 제공합니다. 모델은 기존 기능들에 대한 제어 흐름 (control flow)을 표현하기 위해 작은 프로그램을 작성할 수 있고, 하네스는 호스트 (host)를 통해 어떤 기능들이 사용 가능한지 결정합니다.
설계상 의도된 제한
우리는 이것을 단순한 코드 런타임 (code runtime)이 아니라 인터프리터라고 부르는데, 이는 인터프리터가 의도적으로 제한되어 있기 때문입니다. 기본적으로 일반적인 프로그래밍 환경에서 기대할 수 있는 API들은 갖추고 있지 않습니다. 즉, 파일 시스템 (filesystem), 네트워크 (network), 셸 (shell), 패키지 설치 (package installation), 그리고 실제 시간 (wall-time) 접근 권한이 없습니다. 에이전트는 기본적인 제어 흐름과 객체 조작 (object manipulation) 기능, 즉 객체 (objects), 배열 (arrays), 맵 (maps), JSON, 그리고 기타 작은 언어 런타임 (language runtime) 기능들로 시작합니다.
그러한 기능들은 호스트 런타임 (host runtime)으로의 명시적인 브릿지 (bridges)를 통해 노출됩니다. 만약 에이전트 (agent)가 도구 (tool)를 호출하거나, 범위가 지정된 파일 시스템 API (scoped filesystem API)로부터 읽거나, URL을 가져오거나, 또는 하위 에이전트 (subagent)에게 위임해야 한다면, 하네스 (harness)는 해당 기능을 의도적으로 노출해야 합니다. 예를 들어, 이 스크립트는 fetch, read_file, 그리고 task 도구들을 인터프리터 (interpreter)에 직접 명시적으로 브릿지했을 때만 작동합니다:
// 네트워크 요청을 하기 위해 `fetch` 도구를 호출합니다
const response = tools.fetch("https://docs.langchain.com");
// 에이전트의 파일 시스템에서 파일을 가져오기 위해 `readFile` 도구를 호출합니다
...
호스트 런타임 (host runtime, 하네스를 실행하는 것과 동일한 런타임)은 에이전트가 인터프리터를 사용하여 수행할 수 있는 모든 동작을 포함하며, 인터프리터 코드가 어떤 동작을 호출할 수 있는지 명시적으로 결정합니다. 인터프리터는 해당 경계 (boundary)에서 에이전트의 프로그래밍 가능한 측면입니다.
기본적으로 인터프리터는 샌드박스 (sandbox)가 제공하는 것과 같은 일반적인 호스트 액세스 (host access)가 아닌, 언어 기능 (language features)만으로 시작합니다. 외부 세계와 접촉하는 모든 것은 사용자가 지정한 명시적인 브릿지를 통과해야 합니다.
우리가 이렇게 하는 데에는 몇 가지 이유가 있습니다:
더 작은 공격 표면 (Smaller action surface): bash나 샌드박스 (sandbox)를 사용할 경우, 시작점은 광범위합니다. 에이전트에게 컴퓨터와 유사한 형태의 환경을 제공한 뒤, 그 안에서 할 수 있는 일을 제한하는 방식입니다. 반면 인터프리터 (interpreter)를 사용하면 시작점이 좁습니다. 에이전트에게는 언어 런타임 (language runtime)이 주어지며, 기능들은 의도적으로 하나씩 추가됩니다. 이는 프로세스나 VM 격리가 필요한 위협 모델 (threat model) 상황에서 샌드박싱을 대체하는 것은 아니지만, 에이전트가 기본적으로 광범위한 호스트 접근 권한을 상속받지 않음을 의미합니다.
예측 가능성 (Predictability): 작고 고정된 런타임은 에이전트의 행동을 예측하고 평가하기 더 쉽게 만듭니다. 만약 인터프리터가 광범위한 호스트 접근 권한이나 풍부한 라이브러리 표면 (library surface)을 가지고 있다면, 동일한 목표를 달성하기 위해 수많은 서로 다른 전략을 사용할 수 있으며, 이는 출력을 덜 일관되게 만들고 테스트를 어렵게 합니다. 기본 환경을 최소한으로 유지하고 추가적인 기능들이 명시적인 브릿지 (bridge)를 통과하도록 강제함으로써, 에이전트의 행동 공간 (action space)을 좁히고, 실패 모드 (failure modes)를 명확히 하며, 결과를 더 재현 가능하게 만들 수 있습니다.
Figma, Shopify, AWS 등의 시스템에서도 동일한 아키텍처 구조를 볼 수 있습니다. 한쪽에서는 제약된 코드가 실행되고, 다른 한쪽에서는 호스트가 통제된 API 경계 (API boundary)를 노출합니다.
인터프리터가 가능하게 하는 것들
최근 몇몇 시스템들이 유사한 패턴으로 수렴하고 있습니다. 모델에게 제어 흐름 (control flow)과 중간 상태 (intermediate state)를 관리할 수 있도록 약간의 코드를 작성할 수 있는 작고 범위가 제한된 런타임을 제공하는 것입니다. Cloudflare의 Code Mode, Anthropic의 Programmatic Tool Calling (PTC), 그리고 RLM 스타일의 워크플로 (workflows)는 각각 다른 각도에서 이 아이디어를 가리키고 있습니다. Deep Agents에서 인터프리터는 모델에 구애받지 않는 (model-agnostic) 방식으로 해당 패턴을 구현하는 방법입니다. 다음은 인터프리터가 이미 유용하게 사용되고 있는 몇 가지 사례입니다:
컨텍스트 표면으로서의 인터프리터 상태 (Interpreter state as a context surface)
에이전트 하네스 (Agent harnesses)는 이미 몇 가지 표면을 통해 컨텍스트 (context)를 구성하고 있습니다:
-
메시지 기록 (Message history)은 모델이 즉시 사용할 수 있는 컨텍스트입니다.
-
이는 비용이 많이 들고 어텐션 (attention) 제약이 있습니다. 모델이 백만 개의 토큰을 수용할 수 있다고 해서 모든 토큰에 대해 동일하게 잘 추론한다는 의미는 아닙니다. (예: 컨텍스트 부패 (context rot))
-
파일 시스템 (Filesystem)은 에이전트가 지속적인 산출물 (artifacts), 노트, 중간 파일, 그리고 더 오래 유지되는 작업 메모리 (working memory)를 저장할 수 있는 공간을 제공합니다.
-
이는 내구성이 있고 유연하지만, 에이전트가 작업 상태 (working state)를 파일로 직렬화 (serialize)한 다음 나중에 이를 다시 재구성 (reconstruct)하도록 강제합니다.
-
하네스 (harness)의 역할 중 일부는 파일 시스템과 메시지 기록 (message history) 사이의 컨텍스트 (context) 흐름을 제어하는 것입니다.
인터프리터 상태 (Interpreter state)는 에이전트에게 또 다른 옵션을 제공합니다. 값 (values)들은 런타임 (runtime) 내에 배열 (arrays), 객체 (objects), 맵 (maps), 카운터 (counters), 큐 (queues), 그리고 헬퍼 함수 (helper functions) 형태로 남아 있을 수 있습니다. 모델이 모든 중간 값을 프롬프트 텍스트 (prompt text)로 볼 필요는 없지만, 나중에 인터프리터에게 해당 값들을 검사하거나 재사용하도록 요청할 수는 있습니다.
이는 REPL이 일회성 명령을 실행하는 것과 다르게 느껴지는 이유와 유사합니다. REPL에서 변수를 정의하면, 다음 명령을 제출할 때도 그 변수가 여전히 남아 있습니다. 다음 작업을 수행하기 전에 이를 표준 출력 (stdout)으로 바꾸거나, 파일에 쓰거나, 재구성할 필요가 없습니다. 에이전트가 인터프리터를 여러 번 호출할 때도 동일한 원리가 적용되는데, 이전 호출의 값을 그대로 재사용할 수 있기 때문입니다.
이러한 특성 덕분에 인터프리터는 에이전트 루프 상태 (agent-loop state)에 유용합니다. 메시지 기록 (message history)은 모델이 지금 추론해야 할 내용을 위한 것이고, 파일 시스템은 지속적인 산출물 (artifacts)과 환경 수준의 작업을 위한 것이며, 인터프리터 상태는 나중에 유용할 수 있지만 아직 모델 입력으로 만들 필요는 없는 실시간 작업 값 (live working values)을 위한 것입니다.
프로그래밍 방식의 도구 호출 (Programmatic tool calling)
Anthropic의 프로그래밍 방식의 도구 호출 (Programmatic Tool Calling, PTC)은 이 패턴의 또 다른 버전입니다. 즉, 도구 호출 (tool calls)이 모델이 매개하는 일련의 동작이 아니라, 에이전트가 작성한 코드 내부에서 발생합니다.
만약 모델이 도구를 호출하고, 전체 결과를 받은 뒤, 이를 추론하고, 다음 도구를 호출한다면, 모든 작은 단계가 또 다른 모델 라운드 트립 (round trip)이 됩니다. 만약 에이전트가 도구를 직접 호출하는 코드를 작성할 수 있다면, 중간 출력값들을 런타임 (runtime)에 유지하면서 최종 결과나 선택된 증거만을 반환할 수 있습니다.
Deep Agents에서 PTC는 모델 제공자(model-provider)의 동작이 아닌 미들웨어 (middleware)로 구현됩니다. 개발자가 허용 목록 (allowlist)을 전달하면, 허용된 도구들이 글로벌 tools 네임스페이스 (namespace) 아래에 나타나며, 각 도구는 인터프리터 (interpreter)가 await를 통해 호출할 수 있는 비동기 함수 (async function)로 노출됩니다. 이는 (오픈 소스 모델을 포함하여) 어떠한 모델에 대해서도 PTC를 활성화할 수 있음을 의미합니다.
const topics = ["retrieval", "memory", "evaluation"];
const reports = await Promise.all(
topics.map((topic) =>
...
초기 테스트 중 일부에서, 이러한 방식의 도구 호출 (tool calling)은 특정 작업에서 토큰 (tokens) 사용량을 최대 35%까지 줄였습니다. (우리는 OOLONG의 trec-coarse 데이터셋에서 수집된 작업 세트를 통해 이를 평가했습니다.)
대규모 데이터셋 작업하기
문서 집약적인 작업을 예로 들어보겠습니다. 에이전트가 10,000개의 문서로부터 정보를 분류, 추출 또는 합성해야 하는 상황입니다.
표준적인 도구 호출 (tool-calling) 에이전트의 경우, 자연스러운 형태는 모델이 매개하는 긴 일련의 동작들입니다. 모델은 검색하고, 컨텍스트 (context) 내에서 결과를 돌려받고, 다음에 무엇을 조사할지 결정하고, 다른 도구를 호출하고, 더 많은 결과를 돌려받는 과정을 반복합니다. 작은 작업에서는 이 루프 (loop)로 충분합니다. 하지만 규모가 커지면 다음과 같은 문제로 인해 무너지기 시작합니다:
- 에이전트가 실제로 의도된 절차를 따랐는지 검증하기 어렵습니다.
- 너무 많은 중간 컨텍스트 (intermediate context)가 모델을 통해 다시 전달됩니다.
- 지연 시간 (latency), 컨텍스트 (context) 또는 도구 호출 (tool-call) 제한에 걸리기 쉽습니다.
- 모델이 히스토리 (history)를 통해 너무 많은 작업 상태 (working state)를 관리해야 하므로 응답 품질이 저하될 수 있습니다.
인터프리터 (interpreter) 형태의 버전은 모습이 다릅니다. 모델은 문서 및 검색 상태를 런타임 (runtime)에 유지하고, 프로그래밍 방식으로 배치를 반복하며, 후보를 점수화하거나 필터링하고, 선택된 슬라이스 (slices)에 대해서만 하위 에이전트 (subagents)를 호출하는 코드를 작성할 수 있습니다. 모든 중간 결과를 모델에 반환하는 대신, 인터프리터는 압축된 증거 세트 (evidence set)를 반환합니다. 즉, 매칭된 문서, 추출된 필드, 해결되지 않은 사례, 또는 추론할 가치가 있는 몇 가지 요약본 등을 반환하는 것입니다.
인터프리터(Interpreter)가 마법처럼 10,000개의 문서 전체를 추론하는 것은 아닙니다. 인터프리터는 에이전트(Agent)가 탐색 공간(Search space)을 더 잘 제어하고, 무엇을 모델 컨텍스트(Model context)에 포함시킬지 결정할 수 있는 더 나은 방법을 제공합니다.
const candidates = documents
.map((doc) => ({ doc, score: scoreDocument(doc, query) }))
.filter(({ score }) => score > 0.75)
...
재귀적 오케스트레이션 (Recursive orchestration)
또 다른 관련 개념은 재귀적 언어 모델 (Recursive Language Models, RLMs)입니다. RLMs는 긴 프롬프트(Prompt)를 외부 REPL 환경의 일부로 취급하며, 모델이 코드를 작성하여 선택된 스니펫(Snippet)을 검사, 분해하고 모델을 재귀적으로 호출할 수 있게 합니다.
Deep Agents의 인터프리터는 모델 계층에서 RLMs를 구현하는 것은 아니지만, 하네스(Harness) 수준에서는 여전히 관련성이 있습니다. 즉, 코드가 모델 컨텍스트 외부에서 작업 상태(Working state)를 유지하고, 해당 상태의 일부를 선택하여 다음 모델 또는 서브에이전트(Subagent) 호출 시 그 일부만을 전달할 수 있습니다.
Deep Agents에서 tools.task는 이를 위한 가교 역할을 합니다. 인터프리터 코드는 작업의 일부를 선택하여 이를 서브에이전트에게 위임하고, 그 결과를 기존 런타임 상태(Runtime state)와 결합한 뒤, 합성된 출력값만을 메인 모델에 반환할 수 있습니다.
Deep Agents에서의 작동 방식
하네스 수준에서 인터프리터는 에이전트 루프(Agent loop)와 작은 런타임(Runtime) 사이의 미들웨어(Middleware)입니다. 이 미들웨어는 다음과 같은 역할을 수행합니다:
- 에이전트에
eval도구를 추가합니다 - QuickJS 컨텍스트를 생성하고 유지합니다. - 에이전트의 TypeScript 코드를 실행합니다.
- 설정된 경우
console.log출력을 캡처합니다. - 최종 표현식(Expression)을 모델 컨텍스트로 다시 반환합니다.
eval 도구는 "호스트에서 임의의 코드를 실행하는 것"이 아닙니다. 코드는 인터프리터 컨텍스트 내부에서 실행됩니다. 만약 외부 세계와 통신해야 한다면, 호스트 런타임이 노출하는 브리지(Bridge)를 통해 통신합니다.
프로그래밍 방식의 도구 호출(Programmatic tool calling)이 바로 그러한 호스트 브리지 중 하나입니다. 개발자가 ptc 허용 목록(Allowlist)을 전달하면, 허용된 도구들이 인터프리터 내부의 tools 아래에 나타납니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 LangChain Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기