인터프리터
인터프리터 (Interpreters)
Deep Agents 안에서 가벼운 코드를 실행해 툴을 조합하고 subagent를 오케스트레이션하며 구조화된 데이터를 변환할 수 있는 방법을 보여드릴게요. 인터프리터는 에이전트 루프 안에 프로그래밍 가능한 인메모리(in-memory) 작업 공간을 만들어 줍니다. 에이전트가 과제를 완수하는 코드를 쓰면 런타임이 그걸 실행하고, 관련 결과만 돌려줘요. 중간 결과는 모델 컨텍스트에 남지 않아 토큰 효율이 좋아지죠.
출처: 공식문서
참고로 인터프리터는 beta 상태이며, langchain-quickjs>=0.2.0과 Python 3.11 이상이 필요해요.
왜 인터프리터를 쓸까?
대부분의 에이전트 작업은 모델 추론과 툴 호출을 번갈아 하는 식이에요. 하지만 한 턴에서 모델이 여러 툴 호출을 하더라도 그 배치는 뱉어지는 순간 고정됩니다. 결과에 따라 루프를 돌거나 분기하거나 실패를 재시도하거나, 한 호출의 출력을 다음 호출에 넣으려면 또 모델 턴이 필요하고 모든 결과가 모델 컨텍스트로 돌아가요. 게다가 몇 번 호출할지도 모델이 정하니 수백 개 아이템에 작업을 분산시키는 건 불안정하고, 전부가 아니라 일부만 훑는 경향이 있어요.
인터프리터는 그 오케스트레이션을 코드로 옮겨서, 모델이 모든 중간 단계가 아니라 무엇을 할지만 추론하게 만듭니다.
| 필요 | 사용 |
|---|---|
| 외부 호출 1~2개 | 일반 툴 호출 |
| 순수 인메모리 JavaScript (순환·분기·재시도·데이터 변환) | 인터프리터 |
| 코드에서 오케스트레이션한 여러 외부 툴 호출 | Programmatic tool calling (PTC)를 켠 인터프리터 |
| 독립 작업이 많거나 다각도·대용량 재귀 분석 | dynamic subagents와 함께 |
| 셸 명령·패키지 설치·테스트·OS 파일시스템 | Sandboxes |
퀵스타트
QuickJS 미들웨어 패키지를 설치하고, create_deep_agent의 middleware 인자에 인터프리터 미들웨어를 넘겨줘요.
pip install -U "deepagents[quickjs]"
(uv: uv add "deepagents[quickjs]")
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
middleware=[CodeInterpreterMiddleware()],
)
모델은 공급자 포맷(openai:gpt-5.5, anthropic:claude-sonnet-4-6, openrouter:z-ai/glm-5.2 등)에 맞춰 지정하면 돼요.
인터프리터 작동 원리
미들웨어는 에이전트에 eval 툴을 하나 추가해요. 유용할 때 에이전트가 JavaScript를 쓰고 eval을 호출하면 되고, 직접 인터프리터를 부르는 건 아니에요. 이 툴은 QuickJS 컨텍스트에서 코드를 실행하는데, 변수는 persistence mode에 따라 eval 호출 사이에 유지될 수 있어요. console.log, console.warn, console.error를 캡처하고 마지막 표현식의 결과를 반환합니다.
const rows = [
{ team: "alpha", score: 8 },
{ team: "beta", score: 13 },
{ team: "alpha", score: 21 },
];
const totals = rows.reduce((acc, row) => {
acc[row.team] = (acc[row.team] ?? 0) + row.score;
console.log(`${row.team} score: ${acc[row.team]}`);
return acc;
}, {});
totals;
기본값(mode="thread")에서는 인터프리터 상태가 같은 thread의 턴을 넘어 유지돼요. 코드는 가벼운 JS 런타임인 QuickJS에서 실행되는데, 기본적으로 호스트 파일시스템·네트워크·셸·패키지 매니저·시계에 접근할 수 없어요. 계산과 상태 보유, console.log/warn/error 쓰기만 가능합니다.
그 접근 범위를 넓혀주는 다리는 두 개예요.
- 툴: PTC로
tools네임스페이스 아래에 허용 목록(allowlist) 툴을 async 함수로 노출. - subagent: dynamic subagents로 interpreter가 코드에서 subagent를 호출하는
task()전역을 노출.
PTC는 켜기 전까지 꺼져 있고, task()는 subagent가 있을 때 기본으로 켜져 있어요(끌 수도 있음). 그 외엔 QuickJS 경계를 넘는 게 없어요.
Programmatic tool calling (PTC)
PTC는 선택한 에이전트 툴을 인터프리터 안의 전역 tools 네임스페이스로 노출해요. 모델이 툴 호출 하나 내고 결과 기다렸다 다시 결정하는 대신, 에이전트가 코드로 툴을 루프·분기·재시도·병렬 배치로 호출할 수 있어요. 중간 결과가 다음 단계의 입력일 뿐이라면 인터프리터가 모델로 돌아가기 전에 필터링·집계하므로 멀티스텝 워크플로우 토큰 효율이 좋아집니다. 툴 이름은 camelCase로 바뀌고(web_search → tools.webSearch(...)) 입력 객체는 툴 스키마를 그대로 따릅니다.
const result: string = await tools.webSearch({
query: "deepagents interpreters",
});
PTC 활성화 — 명시적 allowlist로 켜요.
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
middleware=[CodeInterpreterMiddleware(ptc=["web_search"])],
)
병렬 예시:
const topics = ["retrieval", "memory", "evaluation"];
const results = await Promise.all(
topics.map((topic) =>
tools.webSearch({ query: `${topic} best practices 2025` }),
),
);
results.join("\n\n");
⚠️ PTC 호출은 현재 인터프리터 브리지를 거쳐 실행되고 일반 툴 호출 경로를 타지 않아요. 따라서 PTC로 호출한 툴마다
interrupt_on승인 워크플로우는 적용되지 않습니다.
Dynamic subagents
Dynamic subagents는 인터프리터가 내장 task() 전역으로 설정된 subagents를 코드에서 호출하게 해줘요. 디렉토리의 모든 파일을 검토하거나 티켓 배치를 분류하는 것처럼 독립 작업이 많은 작업이, 작업을 팬아웃하고 결과를 종합하는 루프가 됩니다. 활용처는 팬아웃·종합, 검증(독립 검증 subagent에 보내 확인된 결과만 유지), 재귀 워크플로우예요.
const paths = ["src/auth.ts", "src/routes/api.ts"];
const reviews = await Promise.all(
paths.map((path) =>
task({
description: `Review ${path} for authentication issues`,
subagentType: "reviewer",
}),
),
);
reviews.join("\n\n");
Persistence
CodeInterpreterMiddleware의 mode 파라미터로 턴 사이 상태를 제어해요.
"thread"(기본):eval호출과 에이전트 턴을 넘어 상태 유지. 각 턴 후 스냅샷을 찍고 다음 턴 전에 복원."turn": 한 에이전트 턴 안의 여러eval호출에서만 유지되고 다음 턴에 리셋."call":eval호출마다 새 REPL에서 실행, 이전 호출 상태 이월 없음.
mode="thread"에서 스냅샷은 인터프리터의 인메모리 JS 상태(글로벌·변수·함수·import된 모듈)를 직렬화한 복사본이에요. 턴 수명주기는: ① 턴 시작 시 최신 스냅샷 복원 → ② eval 여러 번 호출(하나의 라이브 컨텍스트 공유, 그 사이 스냅샷 없음) → ③ 턴 종료 시 갱신 스냅샷을 그래프 상태에 저장 → ④ 다음 턴이 그 스냅샷에서 재개.
스냅샷은 직렬화 가능한 데이터만 보존해요. 함수·클래스 같은 비직렬화 객체는 복원 후 접근 불가 아티팩트가 됩니다. 접근하면
Value for 'fn' was not restored because it is not serializable (type: function).같은 오류가 나요.
스냅샷은 인터프리터 메모리를 보존하지, 외부 세계의 부수효과를 되돌리진 않아요. PTC로 툴을 호출했다면 이전 스냅샷을 복원해도 그 부수효과는 취소되지 않습니다.
턴 간 persistence는 checkpointer가 없어도 동작해요. 다만 스냅샷이 그래프 상태에 저장되므로 checkpointer를 추가하면 내구성 있는 thread나 time travel도 가능해져요.
from deepagents import create_deep_agent
from langchain_quickjs import CodeInterpreterMiddleware
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
checkpointer=MemorySaver(),
middleware=[CodeInterpreterMiddleware(mode="thread")],
)
보안 (Security)
인터프리터는 QuickJS로 신뢰할 수 없는 JavaScript를 엄격한 기본 격리로 실행해요. 이것을 범위 제한된 인터프리터 런타임으로 보되, 완전한 프로덕션 샌드박스 백엔드로 취급하지 마세요.
PTC로 노출하는 모든 툴은 인터프리터 코드가 쓸 수 있는 외부 능력이에요. PTC allowlist를 권한 경계로 다뤄야 합니다 — 에이전트가 필요한 툴만 노출하고, 민감 시스템 접근·돈 지출·데이터 변조·제한 없는 네트워크에 접근할 수 있는 광범위한 툴은 의도적인 경우가 아니면 브리징하지 마세요.
| 능력 | 기본 제공 | 노출 방법 |
|---|---|---|
| JavaScript 실행 | ✅ | 인터프리터 미들웨어 추가 |
최상위 await |
✅ | 인터프리터 코드에서 promise 사용 |
console.log/warn/error 캡처 |
✅ | capture_console=False로 비활성 |
| 에이전트 툴 | ❌ | PTC allowlist 추가 |
| 파일시스템 접근 | ❌ | 내장 파일시스템 툴을 PTC allowlist로 |
| 네트워크 접근 | ❌ | 특정 네트워크 툴을 PTC로 노출 |
| 시계/날짜 접근 | ❌ | 필요시 명시적 시간 툴 노출 |
| 셸·패키지 설치·테스트·OS 실행 | ❌ | 샌드박스 백엔드 사용 |
인터프리터 코드는 별도 VM·프로세스가 아니라 임베디드 QuickJS 컨텍스트에서 돌아요. Python에서는 quickjs-rs가 제공하며, 같은 프로세스 실행 경계를 Security 가이드에 문서화하고 있어요. 인터프리터를 능력 범위 제한 실행 레이어로 보되 호스트 메모리 격리 경계로는 취급하지 마시고, 신뢰할 수 없거나 반쯤 신뢰하는 코드는 격리된 워커 프로세스/컨테이너에서 에이전트를 돌리고 PTC allowlist를 좁게 유지하세요.
설정 (Configuration)
CodeInterpreterMiddleware 옵션:
| Kwarg | 기본값 | 용도 |
|---|---|---|
memory_limit |
64 * 1024 * 1024 (64 MB) |
thread당 QuickJS 힙 메모리 상한 |
timeout |
5.0 |
eval 호출당 타임아웃(초) |
tool_name |
"eval" |
모델에 노출되는 인터프리터 툴 이름 |
capture_console |
True |
툴 응답에 콘솔 출력 캡처. False면 버림 |
max_result_chars |
4000 |
모델로 돌아가는 결과·오류·stdout 최대 문자수 |
ptc |
None |
tools.*로 노출할 툴 이름·BaseTool 인스턴스 allowlist. 생략 시 비활성 |
max_ptc_calls |
256 |
eval당 tools.* 최대 호출. 신뢰 환경에서만 None |
subagents |
True |
subagent가 있을 때 내장 task() 전역 노출. False면 일반 task 툴로만 |
mode |
"thread" |
persistence 제어: "thread"/"turn"/"call" |
max_snapshot_bytes |
None |
이보다 큰 스냅샷은 버림. 기본 memory_limit |
더 알아보기 (Learn more)
- Dynamic subagents —
task()구성·오케스트레이션·안전 규칙 - Sandboxes — 셸·파일시스템·OS 수준 실행
- QuickJS — 가벼운 JS 런타임
- quickjs-rs 보안 가이드