
monty v0.0.20
AI가 사용하도록 Rust로 작성된 최소형 보안 Python 인터프리터
Monty
AI 사용을 위해 Rust로 작성된 최소한의 안전한 Python 인터프리터입니다.
실험적 - 이 프로젝트는 아직 개발 중이며, 본격적인 사용 준비가 되지 않았습니다.
AI 사용을 위해 Rust로 작성된 최소한의 안전한 Python 인터프리터입니다.
Monty는 LLM이 생성한 코드를 실행하기 위해 전체 컨테이너 기반 샌드박스를 사용할 때 발생하는 비용, 지연 시간, 복잡성 및 번거로움을 피합니다.
대신 에이전트에 내장된 LLM이 작성한 Python 코드를 안전하게 실행할 수 있으며, 시작 시간은 수백 밀리초가 아니라 한 자릿수 마이크로초 단위로 측정됩니다.
Monty가 할 수 있는 것:
- 합리적인 수준의 Python 코드 하위 집합을 실행합니다 - 에이전트가 수행하려는 작업을 표현하기에 충분합니다
- 호스트 환경에 대한 접근을 완전히 차단합니다: 파일 시스템, 환경 변수 및 네트워크 접근은 모두 개발자가 제어할 수 있는 외부 함수 호출을 통해 구현됩니다
- 호스트에서 함수를 호출합니다 - 접근 권한을 부여한 함수만 호출할 수 있습니다
- 타입 검사를 실행합니다 - monty는 최신 Python 타입 힌트를 완전히 지원하며, 타입 검사를 실행하기 위해 단일 바이너리에 ty가 포함되어 있습니다
- 외부 함수 호출 시점에 바이트로 스냅샷을 생성할 수 있습니다. 즉, 인터프리터 상태를 파일이나 데이터베이스에 저장하고 나중에 재개할 수 있습니다
- 시작이 매우 빠르며(<1μs 만에 코드에서 실행 결과까지 도달), 런타임 성능은 CPython과 유사합니다(일반적으로 5배 빠르거나 5배 느립니다)
- Rust, Python 또는 Javascript에서 호출할 수 있습니다 - Monty는 cpython에 대한 의존성이 없으므로 Rust를 실행할 수 있는 모든 곳에서 사용할 수 있습니다
- 리소스 사용량을 제어합니다 - Monty는 메모리 사용량, 스택 깊이, 실행 시간을 추적하고 설정된 한도를 초과하면 실행을 취소할 수 있습니다
- stdout과 stderr를 수집하여 호출자에게 반환합니다
- 호스트에서 비동기 또는 동기 코드를 호스트의 비동기 또는 동기 코드를 통해 실행합니다
- 표준 라이브러리의 작은 하위 집합을 사용합니다:
sys,os,typing,asyncio,re,datetime,json,dataclasses(곧 지원 예정)
Monty가 할 수 없는 것:
- 표준 라이브러리의 나머지를 사용
- 서드파티 라이브러리(예: Pydantic) 사용 - 외부 Python 라이브러리 지원은 목표가 아닙니다
- 클래스 정의 (곧 지원 예정)
- match 문 사용 (이 역시 곧 지원 예정)
요컨대 Monty는 매우 제한적이며 한 가지 사용 사례를 위해 설계되었습니다:
에이전트가 작성한 코드를 실행하는 것.
이러한 방식을 원하는 이유에 대한 동기부여는 다음을 참조하세요:
- Codemode Cloudflare 제공
- Programmatic Tool Calling Anthropic 제공
- Code Execution with MCP Anthropic 제공
- Smol Agents Hugging Face 제공
아주 간단히 말하면, 위의 모든 접근 방식의 핵심 아이디어는 LLM이 전통적인 도구 호출에 의존하는 대신 Python(또는 Javascript) 코드를 작성하도록 요청받을 때 더 빠르고, 저렴하고, 더 안정적으로 작동할 수 있다는 것입니다. Monty는 샌드박스의 복잡성이나 호스트에서 코드를 직접 실행할 때의 위험 없이 이를 가능하게 합니다.
참고: Monty는 곧 Pydantic AI에서 codemode를 구현하는 데 사용될 예정입니다.
사용법
Monty는 Python, JavaScript/TypeScript 또는 Rust에서 호출할 수 있습니다.
Python
설치하려면:```bash uv add pydantic-monty
(또는 구식 사용자를 위한 `pip install pydantic-monty`)
`pydantic-monty`는 `pydantic-monty-client`(즉 `pydantic_monty` 모듈)와 `pydantic-monty-runtime`(즉 `monty` 워커 바이너리)을 함께 묶는 메타패키지입니다. 바이너리가 이미 다른 곳에서 제공되는 경우 `pydantic-monty-client`만 설치하세요.
사용법:```python
from typing import Any
import pydantic_monty
code = """
async def agent(prompt: str, messages: Messages):
while True:
print(f'messages so far: {messages}')
output = await call_llm(prompt, messages)
if isinstance(output, str):
return output
messages.extend(output)
await agent(prompt, [])
"""
type_definitions = """
from typing import Any
Messages = list[dict[str, Any]]
async def call_llm(prompt: str, messages: Messages) -> str | Messages:
raise NotImplementedError()
prompt: str = ''
"""
Messages = list[dict[str, Any]]
async def call_llm(prompt: str, messages: Messages) -> str | Messages:
if len(messages) < 2:
return [{'role': 'system', 'content': 'example response'}]
else:
return f'example output, message count {len(messages)}'
async def main():
async with pydantic_monty.AsyncMonty() as pool:
async with pool.checkout(
script_name='agent.py',
type_check=True,
type_check_stubs=type_definitions,
) as session:
output = await session.feed_run(
code,
inputs={'prompt': 'testing'},
external_lookup={'call_llm': call_llm},
)
print(output)
#> example output, message count 2
if __name__ == '__main__':
import asyncio
asyncio.run(main())
실행은 monty 워커 하위 프로세스 풀에서 이루어지므로, 심지어 메모리
오류(적대적 코드에 의해 유발된 스택 오버플로, 할당자 중단)조차도 프로세스를
결코 충돌시키지 못합니다 — 워커가 죽으면 MontyCrashedError가 발생하고
교체됩니다. 또한 완전히 동기식 API도 있습니다:```python
import pydantic_monty
with pydantic_monty.Monty() as pool: with pool.checkout() as session: # session state persists between feed_run calls session.feed_run('x = 21') print(session.feed_run('x * 2')) #> 42
### JavaScript / TypeScript
설치하려면:```bash
npm install @pydantic/monty
JS 패키지는 Python 패키지가 사용하는 것과 동일한 Rust 워커 풀에 대한
네이티브(napi) 바인딩입니다 — 바인딩과 monty 워커 바이너리는
플랫폼별 npm 패키지를 통해 배포됩니다:```ts
import { Monty } from '@pydantic/monty'
await using pool = await Monty.create() await using session = await pool.checkout()
// session state persists between feedRun calls await session.feedRun('x = 21') console.log(await session.feedRun('x * 2')) // 42
// external functions may be async const result = await session.feedRun('await fetch_data()', { externalLookup: { fetch_data: async () => 'data' }, })
브라우저(또는 서브프로세스를 사용할 수 없는 모든 환경)의 경우 동일한 패키지는
`@pydantic/monty/wasm` 하위 경로 아래에 프로세스 내 WebAssembly 빌드를 제공합니다.
이 하위 경로에는 충돌 격리가 없습니다(샌드박스 충돌은 곧 호스트 충돌입니다).
### Rust
Rust에서 신뢰할 수 없는 코드를 실행하려면 아래의 프로세스 내 API보다
[`monty-pool`](https://crates.io/crates/monty-pool) 크레이트를 권장합니다.
`monty-pool`은 `monty` 워커 서브프로세스에서만 코드를 실행하므로 추가 보호를 제공합니다:
적대적 코드로 유발된 충돌(스택 오버플로, 할당자 중단)은 워커만 죽이며 —
풀은 그 죽음을 감지하고 워커를 교체합니다 — 그리고 상위 프로세스의 워치독은 하드 타임아웃을
초과하는 워커를 종료할 수 있습니다. 이는 위의 Python 및 JavaScript 패키지가
기반으로 하는 것과 동일한 엔진입니다. 사용법은 [monty-pool README](https://github.com/pydantic/monty/tree/main/crates/monty-pool)을
참조하세요.
`monty` 크레이트 자체가 프로세스 내 인터프리터를 제공합니다:```rust
use monty::MontyRun;
use monty_types::{CompileOptions, ResourceTracker, MontyObject, PrintWriter, ResourceLimits};
let code = r#"
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
fib(x)
"#;
let runner = MontyRun::new(code.to_owned(), "fib.py", vec!["x".to_owned()], CompileOptions::default()).unwrap();
let result = runner.run(vec![MontyObject::Int(10)], ResourceTracker::default(), PrintWriter::Stdout).unwrap();
assert_eq!(result, MontyObject::Int(55));
직렬화
REPL 세션은 dump()로 직렬화하고 Dump::load()로 복원할 수 있습니다. 덤프는 로딩 빌드가 확인하는 버전 뒤에서, 인터프리터 상태와 함께 세션 메타데이터(스크립트 이름, 타입 검사 스텁)를 담고 있습니다:```rust
use monty::{Dump, MontyRepl, Session, SessionRef, dump};
use monty_types::{CompileOptions, MontyObject, PrintWriter, ResourceTracker};