用 Rust 打造 Python 界的「蒙面俠」:Monty,為 AI 程式碼安全護航
你是否曾好奇,AI 能夠寫程式,但我們如何安全地執行它?特別是在處理像是 LLM (Large Language Model) 產生的程式碼時,如何避免潛在的風險?今天,我要為你介紹一個專為這個需求而生的新工具:Monty。它是一個用 Rust 寫成的 Python 直譯器,設計用於在 AI 應用中安全地執行程式碼。
什麼是 Monty?為什麼你需要它?
簡單來說,Monty 就像一個「蒙面俠」,它能讓你安全地執行 AI 生成的 Python 程式碼,而無需使用大型的容器(例如 Docker),也不用擔心複雜的設定和潛在的風險。
主要優點:
- 速度快: 啟動速度極快,以微秒為單位。
- 安全: 完全控制檔案系統、環境變數和網路存取,確保程式碼在一個受控的環境中執行。
- 靈活: 能夠呼叫主機上的函數,但只能是經過你允許的函數。
- 可序列化: 可以儲存直譯器的狀態,稍後再恢復。
- 資源控制: 監控記憶體使用、配置、堆疊深度和執行時間,防止程式碼失控。
Monty 可以做什麼?
- 執行 Python 程式碼的子集,足以讓 AI 表達其意圖。
- 完全禁止訪問主機環境,包括檔案系統、環境變數和網路。
- 呼叫主機上的函數,僅限於你授權的函數。
- 進行型別檢查,支援現代 Python 型別提示。
- 可以將直譯器狀態保存到檔案或資料庫,並稍後恢復。
- 啟動速度極快 (<1 微秒)。
- 可以從 Rust、Python 或 Javascript 呼叫。
- 控制資源使用。
- 收集 stdout 和 stderr,並將其返回給呼叫者。
- 透過主機上的 async 或 sync 程式碼,執行 async 或 sync 程式碼。
Monty 不能做什麼?
- 使用標準庫(除了
sys、typing、asyncio、dataclasses(即將推出)、json(即將推出)等少數模組)。 - 使用第三方函式庫(如 Pydantic)。
- 定義類別(支援即將推出)。
- 使用 match 語句(支援即將推出)。
為什麼選擇 Monty?
想像一下,你正在開發一個 AI 助手,它需要撰寫程式碼來完成特定任務。如果直接在你的系統上執行這些程式碼,可能會帶來潛在的安全風險。這正是 Monty 的用武之地。
- 快速又安全: 相較於使用完整的沙箱環境,Monty 提供了更快的啟動速度和更安全的執行環境。
- 專為 AI 打造: Monty 專注於滿足 AI 程式碼執行的特定需求,例如工具呼叫、程式碼模式等。
- 減少複雜性: 避免了沙箱的複雜性,讓你能更輕鬆地執行 AI 產生的程式碼。
如何開始使用 Monty?
使用 Monty 相當簡單,你可以透過 Python、JavaScript/TypeScript 或 Rust 來呼叫它。
1. Python 快速上手
首先,你需要安裝 pydantic-monty:
uv add pydantic-monty
或者,如果你是傳統派:
pip install pydantic-monty
接著,看看這個簡單的 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 = ''
"""
m = pydantic_monty.Monty(
code,
inputs=['prompt'],
external_functions=['call_llm'],
script_name='agent.py',
type_check=True,
type_check_stubs=type_definitions,
)
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():
output = await pydantic_monty.run_monty_async(
m,
inputs={'prompt': 'testing'},
external_functions={'call_llm': call_llm},
)
print(output)
#> example output, message count 2
if __name__ == '__main__':
import asyncio
asyncio.run(main())
這個範例展示了如何使用 Monty 來執行一段 AI 程式碼,並與外部函數 call_llm 互動。
2. 迭代執行與外部函數
如果你需要更細緻的控制,可以使用 start() 和 resume() 來迭代地執行程式碼,並處理外部函數的呼叫:
import pydantic_monty
code = """
data = fetch(url)
len(data)
"""
m = pydantic_monty.Monty(code, inputs=['url'], external_functions=['fetch'])
# 啟動執行 - 當 fetch() 被呼叫時暫停
result = m.start(inputs={'url': 'https://example.com'})
print(type(result))
#> <class 'pydantic_monty.MontySnapshot'>
print(result.function_name) # fetch
#> fetch
print(result.args)
#> ('https://example.com',)
# 執行實際的 fetch,然後使用結果恢復
result = result.resume(return_value='hello world')
print(type(result))
#> <class 'pydantic_monty.MontyComplete'>
print(result.output)
#> 11
3. 序列化
Monty 支援將程式碼和執行狀態序列化為位元組,方便儲存或跨進程傳輸:
import pydantic_monty
# 序列化已解析的程式碼,以避免重新解析
m = pydantic_monty.Monty('x + 1', inputs=['x'])
data = m.dump()
# 稍後,恢復並執行
m2 = pydantic_monty.Monty.load(data)
print(m2.run(inputs={'x': 41}))
#> 42
# 序列化執行狀態
m = pydantic_monty.Monty('fetch(url)', inputs=['url'], external_functions=['fetch'])
progress = m.start(inputs={'url': 'https://example.com'})
state = progress.dump()
# 稍後,恢復並恢復(例如,在不同的進程中)
progress2 = pydantic_monty.MontySnapshot.load(state)
result = progress2.resume(return_value='response data')
print(result.output)
#> response data
4. Rust 範例
你也可以使用 Rust 來呼叫 Monty:
use monty::{MontyRun, MontyObject, NoLimitTracker, StdPrint};
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()], vec![]).unwrap();
let result = runner.run(vec![MontyObject::Int(10)], NoLimitTracker, &mut StdPrint).unwrap();
assert_eq!(result, MontyObject::Int(55));
Rust 中的序列化
use monty::{MontyRun, MontyObject, NoLimitTracker, StdPrint};
// 序列化已解析的程式碼
let runner = MontyRun::new("x + 1".to_owned(), "main.py", vec!["x".to_owned()], vec![]).unwrap();
let bytes = runner.dump().unwrap();
// 稍後,恢復並執行
let runner2 = MontyRun::load(&bytes).unwrap();
let result = runner2.run(vec![MontyObject::Int(41)], NoLimitTracker, &mut StdPrint).unwrap();
assert_eq!(result, MontyObject::Int(42));
Monty 與 Pydantic AI 的整合
Monty 將成為 Pydantic AI 中代碼模式的核心。這意味著,AI 將可以編寫 Python 程式碼,來呼叫你的工具函數,而 Monty 將安全地執行這些程式碼。
“`python test=”skip”
from pydantic_ai import Agent
from pydantic_ai.toolsets.code_mode import CodeModeToolset
from pydantic_ai.toolsets.function import FunctionToolset
from typing_extensions import TypedDict
class WeatherResult(TypedDict):
city: str
temp_c: float
conditions: str
toolset = FunctionToolset()
@toolset.tool
def get_weather(city: str) -> WeatherResult:
"""Get current weather for a city."""
# 這裡放你的真實實現
return {'city': city, 'temp_c': 18, 'conditions': 'partly cloudy'}
@toolset.tool
def get_population(city: str) -> int:
"""Get the population of a city."""
return {'london': 9_000_000, 'paris': 2_100_000, 'tokyo': 14_000_000}.get(
city.lower(), 0
)
toolset = CodeModeToolset(toolset)
agent = Agent(
'anthropic:claude-sonnet-4-5',
toolsets=[toolset],
)
result = agent.run_sync(
'Compare the weather and population of London, Paris, and Tokyo.'
)
print(result.output)
“`
Monty 的替代方案
當然,Monty 並不是唯一的選擇。以下是一些替代方案,以及它們的優缺點:
| 技術 | 語言完整性 | 安全性 | 啟動延遲 | FOSS | 設置複雜性 | 檔案掛載 | 快照 |
|---|---|---|---|---|---|---|---|
| Monty | 部分 | 嚴格 | 0.06ms | free / OSS | 簡單 | 容易 | 容易 |
| Docker | 完整 | 良好 | 195ms | free / OSS | 中等 | 容易 | 中等 |
| Pyodide | 完整 | 差 | 2800ms | free / OSS | 中等 | 容易 | 困難 |
| starlark-rust | 非常有限 | 良好 | 1.7ms | free / OSS | 簡單 | 無? | 無? |
| WASI / Wasmer | 部分,幾乎完整 | 嚴格 | 66ms | free * | 中等 | 容易 | 中等 |
| 沙箱服務 | 完整 | 嚴格 | 1033ms | 非免費 | 中等 | 困難 | 中等 |
| YOLO Python | 完整 | 不存在 | 0.1ms / 30ms | free / OSS | 簡單 | 容易 / 恐怖 | 困難 |
備註: 上述啟動延遲數字是根據 ./scripts/startup_performance.py 腳本計算的。
簡要說明:
- Monty: 針對 AI 程式碼執行優化,安全、快速、易於使用,但語言支援有限。
- Docker: 完整 Python 支援,安全可靠,但啟動速度慢。
- Pyodide: 在瀏覽器中執行 Python,啟動速度慢,安全性較差。
- starlark-rust: 配置語言,啟動快速,但功能有限。
- WASI / Wasmer: WebAssembly 中的 Python,安全性好,但啟動速度較慢。
- 沙箱服務: 提供容器隔離,安全性好,但成本較高,設置較複雜。
- YOLO Python (直接執行): 速度快,但安全性差,不推薦。
結論
Monty 是一個很有潛力的工具,它為在 AI 應用中安全地執行程式碼提供了新的可能性。 如果你正在尋找一個快速、安全且專為 AI 設計的 Python 直譯器,Monty 絕對值得一試。 隨著 Monty 的不斷發展,相信它將在 AI 程式碼執行領域扮演越來越重要的角色。
參考閱讀
https://github.com/pydantic/monty