news 2026/9/16 18:02:36

pydantic-monty 实战指南:用 Monty 沙箱在 Python 宿主进程中安全执行不可信代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pydantic-monty 实战指南:用 Monty 沙箱在 Python 宿主进程中安全执行不可信代码

pydantic-monty 实战指南:用 Monty 沙箱在 Python 宿主进程中安全执行不可信代码

【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty

pydantic-monty 是 Monty(一个用 Rust 编写、专为 AI 场景设计的最小化安全 Python 解释器)的 Python 绑定包。它以子进程池架构隔离执行所有沙箱代码:即使沙箱内发生段错误、栈溢出或分配器崩溃,宿主进程也毫发无损,崩溃的 worker 会被透明替换并抛出MontyCrashedError。读完本文,你将掌握如何安装并配置 pydantic-monty,使用Monty/AsyncMonty运行同步与异步沙箱代码,通过ClassInstance/ClassType安全地向沙箱暴露宿主对象,并利用快照(snapshot)机制暂停、序列化、跨进程恢复执行。

架构基础:为什么执行必须发生在子进程池中

pydantic-monty是 Monty 的 Python 官方绑定,其核心理念写在其文档第一段:执行永远发生在一组montyworker 子进程中。原因很直接——Monty 进程无法对内存类错误(stack overflows、allocator aborts)做到完全崩溃免疫,尤其是面对对抗性输入时。因此崩溃隔离被内建为架构特性:某个 worker 崩溃后,池会透明地替换它,你的进程永远处于安全侧。相关的行为细节(宿主侧挂载、带缓冲的 print 回调、会话 dump)记录在仓库的 limitations/pool-architecture.md 中。

从 packages/pydantic-monty/pyproject.toml 可以看到,pydantic-monty本身是一个metapackage(元包):它自身不携带任何代码,只负责把构成一个可用沙箱的两个发行版精确固定为同一版本(当前均为0.0.22)一起安装:

  • pydantic-monty-client—— 你实际importpydantic_monty模块(worker 池、会话、值转换),源码位于 crates/monty-python;
  • pydantic-monty-runtime—— 池要拉起的montyworker 二进制,以与uvruff相同的方式随 wheel 分发(maturin 的 bin 绑定模式)。

pyproject.toml[project.scripts]声明的pydantic-monty = "pydantic_monty._cli:main"是唯一由元包提供的可执行入口。值得注意的实现细节是:这个 console script刻意不叫monty,否则会与pydantic-monty-runtime安装到 scripts 目录里的真实二进制同名冲突、静默覆盖它,导致后续的二进制查找解析到 shim 自身,见 crates/monty-python/python/pydantic_monty/_cli.py 的模块文档。

安装与二进制定位

标准安装方式(二选一):

uv add pydantic-monty # or pip install pydantic-monty

元包会同时拉入 client 与 runtime 两个发行版。需要安装 OpenTelemetry 可观测性支持时使用:

pip install 'pydantic-monty[opentelemetry]'

只安装 client 的场景

当 worker 二进制来自其他渠道——基础镜像(base image)、系统包、或本仓库直接cargo build出来的产物——可以只安装pydantic-monty-client,然后通过三种方式之一让pydantic_monty找到它:

  • 环境变量MONTY_BIN
  • Monty(binary_path=...)构造参数
  • 将二进制放在PATH

pydantic-monty-client的独立安装说明见 crates/monty-python/README.md,它同时支持单独用于连接远程monty-server(WebSocket 传输)。

二进制解析顺序

find_monty_binary 实现了完整的查找链,按优先级排列:

  1. 显式传入的binary_path参数;
  2. MONTY_BIN环境变量;
  3. 当前环境的 scripts 目录(pydantic-monty-runtimewheel 默认安装位置),会同时检查sysconfig.get_path('scripts')--user方案目录;
  4. PATH上名为monty(Windows 下为monty.exe)的可执行文件;
  5. 开发模式兜底:当以可编辑安装运行在本仓库内时,回退到<repo>/target/{debug,release}/monty中最近构建的那个(cargo buildcargo build --release都能被正确感知)。

全部失败则抛出FileNotFoundError,提示安装pydantic-monty(或在仓库内执行make dev-py)、传入binary_path=...或设置MONTY_BIN

CLI:不安装也能用,安装了更好用

不安装任何东西,直接用 uvx 运行:

uvx pydantic-monty --help

uvx pydantic-monty不带参数时进入 REPL,带文件参数时执行该文件:uvx pydantic-monty <file>

或者把monty装到本地使用:

uv tool install pydantic-monty-runtime # 然后运行 REPL: monty # 或运行文件: monty <file> # 或查看帮助: monty --help

在已安装pydantic-monty的环境中,python -m pydantic_monty会运行同一个二进制。其实现(__main__.py)只是转发参数:POSIX 上直接os.execv替换进程(信号与退出码归二进制自身所有),Windows 上没有 exec 语义,则subprocess.Popen等待子进程并在等待循环中吞掉KeyboardInterrupt,避免 Ctrl-C 提前退出而把二进制孤儿化在终端上。

基本执行:池、会话与 feed_run

最简用法:

from pydantic_monty import Monty with Monty() as pool: with pool.checkout() as session: print(session.feed_run('1 + 2')) #> 3

这里有三层概念:

  • Monty()是一个worker 池(context manager,with进入时才真正拉起子进程);
  • pool.checkout()把池中一个专用 worker 租给一个 REPL 会话
  • session.feed_run(code)在会话内执行一段代码并返回末表达式的值。

会话状态(全局变量、函数定义)跨feed_run调用持续存在:

from pydantic_monty import Monty with Monty() as pool: with pool.checkout() as session: session.feed_run('x = 40') print(session.feed_run('x + 2')) #> 42

Monty 池的构造参数

依据 crates/monty-python/python/pydantic_monty/_monty.pyi 中的签名,Monty()支持以下关键字参数:

参数默认值含义
binary_pathNonemontyCLI 二进制路径;省略时按上文解析顺序查找
min_processes1预热并常驻的 worker 数
max_processesCPU 核数存活 worker 上限;超出时 checkout 排队等待 worker 归还
checkout_timeoutNone(永远等待)checkout()等待空闲 worker 的秒数,超时抛TimeoutError
request_timeoutNone宿主侧每轮(turn)截止时间;worker 超时会被杀死,调用抛timed_out=TrueMontyCrashedError
max_checkouts_per_workerNoneworker 服务完这么多会话后被回收

checkout 的会话级参数

checkout()返回的MontySession同样是 context manager,with块退出时 worker 归还池中。其参数包括:

  • script_name(默认'main.py'):出现在 traceback 与错误消息里的文件名;
  • limits:在 worker 内部强制执行的资源限制(见下文专节),其中max_suspensions由池侧自行强制;
  • type_check/type_check_stubs/type_check_format/type_check_color:类型检查开关与诊断渲染选项(见下文专节);
  • assert_message_annotations(默认开启):让失败的assert输出 pytest 风格的内省消息(如AssertionError: assert 2 == 5),这是相对 CPython 空消息AssertionError的有意分歧;设为False恢复 CPython 行为,设为int >= 1可自定义每个操作数 repr 的截断长度(默认 120 字节);
  • print_flush_interval(默认0.005秒):worker 最多缓存多久的print()输出再统一发送,使大量 print 只产生一次回调而非每次一次;设为0恢复行缓冲(每完成一行就投递)。输出在任何宿主调用前和运行结束前总是被冲刷,因此该参数只影响实时性,不影响最终送达内容与顺序。

异步执行:AsyncMonty

AsyncMontyMonty的 asyncio 对应物:worker I/O 在事件循环之外运行,且external_lookup中的外部函数可以是协程。其池构造参数与Monty完全一致(见 crates/monty-python/python/pydantic_monty/_monty.pyi 中AsyncMonty.__new__)。

import asyncio from pydantic_monty import AsyncMonty async def fetch(url: str) -> str: await asyncio.sleep(0.01) return f'contents of {url}' async def main(): async with AsyncMonty() as pool: async with pool.checkout() as session: result = await session.feed_run( "await fetch('https://example.com')", external_lookup={'fetch': fetch}, ) print(result) #> contents of https://example.com asyncio.run(main())

协程外部函数会被并发等待,并通过AsyncFutureSnapshot结算。

输入变量与外部查找

feed_run提供两种向沙箱注入宿主值的机制:

  • inputs(急切绑定):在代码运行前就把每个条目转换并绑定为全局变量——无论代码是否引用,都会绑定一次;
  • external_lookup(惰性按需解析):可调用条目成为沙箱可调用的宿主函数;任何其他值在名字被读取时转换并返回;缺失的名字抛NameError。同名时由急切的inputs绑定优先。
from pydantic_monty import Monty with Monty() as pool: with pool.checkout() as session: result = session.feed_run( 'double(x) + y', inputs={'x': 5, 'y': 1}, external_lookup={'double': lambda x: x * 2}, ) print(result) #> 11

feed_run还接受print_callback(默认输出到宿主进程的 stdout/stderr,也可传CollectStreams/CollectString收集器,二者默认 10 MiB 上限、传max_bytes=None可禁用)、mount(宿主目录挂载,见下)、os(未被挂载覆盖的 OS 调用兜底处理器)与skip_type_check。需要强调的是,feed_run会阻塞调用线程(期间释放 GIL),异步外部函数在此不受支持——请用AsyncMonty

宿主对象与类:ClassInstance / ClassType

沙箱中的代码可能需要操作宿主进程中的对象。pydantic-monty用两个包装器以白名单策略控制暴露面,实现位于 crates/monty-python/python/pydantic_monty/class_instance.py。

from dataclasses import dataclass from pydantic_monty import ClassInstance, ClassType, Monty @dataclass class Person: name: str age: int def greeting(self) -> str: return f'hi {self.name}' person = Person(name='Samuel', age=4) with Monty() as pool: with pool.checkout() as session: wrapper = ClassInstance(person, eager_attrs='all', allowed_methods={'greeting'}) code = 'assert user.greeting() == "hi Samuel"\nuser' result = session.feed_run(code, inputs={'user': wrapper}) print(result is person) #> True wrapper = ClassType(Person, init=True, instance_eager_attrs='all') print(session.feed_run('Person("Ada", 36).name', inputs={'Person': wrapper})) #> Ada

策略语义

ClassInstance包装一个实例,是纯宿主侧策略:它决定哪些属性急切跨边界eager_attrs)、哪些可以惰性按需获取lazy_attrs)、哪些方法允许沙箱调用allowed_methods)。三者取值均可为:

  • None:什么都不暴露;
  • 'all':暴露包装器判定为“公开”的一切——实例侧为 dataclass 字段(或__dict__条目与__slots__)中不以_开头的名字;类侧为类__dict__中公开的非常量项(方法/描述符被_is_class_machinery排除,因此'all'只发送普通类常量);
  • 一组名字的可迭代对象:精确暴露这些名字。

注意:'all'之外,任何裸字符串都会抛TypeError——防止把'ab'误当作字符集合而意外暴露每个子串。方法白名单下,'all'只放行定义在类上的函数(嵌套类或存储为属性的可调用对象不算);实例的__call__永远被拒绝(只有ClassType接受它,作为构造语义)。

类的构造能力

ClassType是类级包装器:init=True允许沙箱代码实例化该类。构造以一次__call__方法调用到达宿主侧,在宿主机执行后,结果按instance_*策略(instance_eager_attrsinstance_lazy_attrsinstance_allowed_methods)包装成ClassInstance送回沙箱。ClassType.method_allowed只放行 classmethod/staticmethod——实例方法经类对象调用会拿任意沙箱值当self,故一律拒绝。类还带有稳定的会话内身份:ClassInstance默认携带ClassType(type(value))作为其class_type,因此沙箱内的type(x)与作为值传入的ClassType是同一个类型对象。

convert_value 钩子与返回值包装

方法返回值不会自动包装:派生对象要按你选择的策略继续暴露,需要覆写convert_value(每个包装器会被会话保留到关闭)。例如在ClassInstance.convert_value中返回ClassInstance(value, eager_attrs='all'),即可为返回的派生实例套上新的策略。ClassType覆写了convert_value并默认由其实例委托,因此一个在构造路径上做了脱敏/包装的ClassType子类,其构造出的实例与随其发送的实例都会走同一套钩子。沙箱内部定义的实例到达宿主侧时是只读的MontyClassProxy占位(保留实例的id,再次传回沙箱可还原为同一存活对象)。完整的设计说明见 docs/host-objects.md。

快照:暂停与恢复执行

feed_startfeed_run可挂起对应物:它不把一段代码一路驱动到底,而是在每次外部调用、OS 调用、名字查找或 future 结算处把控制权以snapshot(快照)形式交还宿主。你用snapshot.resume(...)应答,它返回下一个快照或MontyComplete

from pydantic_monty import FunctionSnapshot, Monty, MontyComplete with Monty() as pool: with pool.checkout() as session: snapshot = session.feed_start('greet(name) + "!"', inputs={'name': 'Ada'}) assert isinstance(snapshot, FunctionSnapshot) print(snapshot.function_name, snapshot.args) #> greet ('Ada',) result = snapshot.resume({'return_value': 'hello Ada'}) assert isinstance(result, MontyComplete) print(result.output) #> hello Ada!

快照类型

  • FunctionSnapshot:等待一次外部函数或 OS 调用的结果。is_os_functionTruefunction_nameOsFunction名,可resume_not_handled()交给 Monty 的默认未处理行为;object_id指向被路由的宿主对象(经ClassInstance/ClassType发送的接收者,不包含在args中)。
  • NameLookupSnapshot:等待一个未定义名字的值,或(object_id被设置时)宿主对象上的惰性属性查找。resume()省略value时沙箱内抛NameError(属性查找则抛AttributeError)。
  • FutureSnapshot:所有沙箱任务都阻塞在外部 future 上(协程外部函数的同步会话场景)。用pending_call_ids+resume({call_id: result})按 id 结算;resume_auto()在同步会话中恒抛RuntimeError
  • MontyComplete:完成态,output属性给出最终值。

自动驱动:resume_auto

不想手工应答每个挂起点时,给feed_start传入external_lookup(和/或os),然后用snapshot.resume_auto()驱动——它会从这些表中自动解析每次外部调用与名字查找,这正是feed_run所做的解析,只不过一次一步,让你能沿途检查甚至dump()每个快照:

from pydantic_monty import Monty, MontyComplete with Monty() as pool: with pool.checkout() as session: snapshot = session.feed_start( 'greet(name) + "!"', inputs={'name': 'Ada'}, external_lookup={'greet': lambda n: f'hello {n}'}, ) while not isinstance(snapshot, MontyComplete): snapshot = snapshot.resume_auto() print(snapshot.output) #> hello Ada!

要点:feed_start的初始驱动阶段消费external_lookup——外部调用与名字查找仍以快照形式浮出,该表只是被捕获供后续resume_auto()使用。异步场景中,AsyncMonty上的external_lookup可含协程函数,resume_auto变为可等待(snapshot = await snapshot.resume_auto()),协程外部被并发等待并经AsyncFutureSnapshot结算;但经load_snapshot恢复的FutureSnapshot的挂起协程已随旧进程消失,其resume_auto()会抛错,需手工resume({call_id: ...})结算。

序列化:dump 与恢复

snapshot.dump()把暂停中的 worker 序列化为字节;新会话的load_snapshot恢复它并返回待恢复的快照。这使得执行可以被检查点化并在之后继续——甚至可以跨进程

from pydantic_monty import FunctionSnapshot, Monty, MontyComplete with Monty() as pool: with pool.checkout() as session: snapshot = session.feed_start( 'fetch(url)', inputs={'url': 'https://example.com'} ) blob = snapshot.dump() # 稍后——恢复到新会话并继续 with pool.checkout() as session: snapshot = session.load_snapshot(blob) assert isinstance(snapshot, FunctionSnapshot) result = snapshot.resume({'return_value': 'page contents'}) assert isinstance(result, MontyComplete) print(result.output) #> page contents

注意事项(来自load_snapshot的文档):

  • 如果被暂停的 feed 使用了文件系统mount,恢复时必须向load_snapshot(blob, mount=...)重新提供相同的挂载——dump 不存储宿主路径;且 dump 前的 overlay 写入不会保留(恢复后的 overlay 从空开始)。
  • session.dump()(两次 feed 之间)序列化的是空闲会话,用session.load_session(blob)恢复(返回None),之后可继续喂代码。
  • load_sessionload_snapshot只对全新会话(尚未有任何 feed)有效,用错类型会抛错。
  • dump 恢复自己的script_name/limits/类型检查状态(checkout()的对应配置不生效);类实例存储是宿主状态、从不入 dump,因此恢复后的ClassInstance值会退化为MontyClassProxy占位,对其的方法调用会失败。
  • AsyncMonty会话暴露同样的feed_start/load_session/load_snapshotresume(...)可等待。

资源限制

限制在worker 内部强制执行,而池的request_timeout是宿主侧兜底——直接杀死卡死的 worker。已安装的遥测会同步调用受信任的 Python SDK 回调,此类回调运行期间强制执行被推迟。

checkout(limits={...})接受 ResourceLimits(一个 TypedDict),完整字段如下:

字段类型含义
max_duration_secsfloat \| None最大执行时间(秒)
max_memoryint \| None最大堆内存(字节)
gc_intervalint \| None每 N 次分配运行一次垃圾回收
max_recursion_depthint \| None最大函数调用栈深度(默认 1000,不可禁用)
max_suspensionsint \| None每个 checkout 内外部调用/os回调/名字查找/future 结算的最大次数(默认 1000,不可禁用;超出后 feed 被不可捕获的RuntimeError中止,会话仍可用,恢复 dump 会重置计数)

省略某个键或显式设为None即禁用该限制(上述两个例外不可禁用)。

max_duration_secs 的精确语义

max_duration_secs限制的是累积执行时间——时钟只在解释器执行时走动,暂停等待宿主时不计时,且跨 feed 累积。worker 在每次协议轮次上报执行时间;设了限制的会话在剩余预算耗尽后还会被额外杀死,宽限为duration_limit_grace(1 秒,当前不可从 Python 配置)——这覆盖了沙箱内限制无法捕获的挂起(其检查只在解释器检查点运行)。

from pydantic_monty import Monty, MontyRuntimeError with Monty(request_timeout=10) as pool: with pool.checkout(limits={'max_duration_secs': 0.1}) as session: try: session.feed_run('while True:\n pass') except MontyRuntimeError as exc: print(exc.display(format='type-msg').split(':')[0]) #> TimeoutError

挂载(MountDir)

宿主目录通过MountDir挂入沙箱(构造参数全部为关键字参数,避免 docker-v与 nginxalias在“宿主在前还是虚拟在前”上的分歧):

  • host_path:真实宿主目录,构造时即打开并校验;沙箱代码永远看不到该路径、也到不了其外部。目录内符号链接只跟随相对目标,绝对目标即使指回同一挂载也会在沙箱内抛PermissionError
  • virtual_path:沙箱内的绝对 POSIX 风格路径前缀(如'/data'),与宿主操作系统无关。
  • mode'read-only'(写入抛PermissionError)/'read-write'(穿透写入宿主目录并持久化,警告:不可信代码写的文件属于不可信输入,不要执行它们——若该目录在sys.path上,沙箱代码可写入json.py之类的模块名并借后续 import 执行,包括 pydantic_monty 自身发起的 import)/'overlay'(默认:读穿透到宿主,写按 feed 隔离在内存中、feed 结束时丢弃)。
  • write_bytes_limit:单 feed 内累计写入字节上限,超出在沙箱内抛OSErrorNone表示不限。
  • memory_usage_limit(默认 100 MB):每个挂载的内存预算,由保留的 overlay 数据与临时文件系统结果共享,超出抛MemoryError

目录从构造起保持打开直到close()(幂等;Windows 上不关闭会阻止宿主重命名/删除该目录),可跨 feed 复用、也可作为 context manager 使用;把已关闭的 mount 传给 feed 会抛ValueError。使用示例:

from pathlib import Path from pydantic_monty import Monty, MountDir with Monty() as pool, MountDir(host_path=Path('host-dir'), virtual_path='/data') as mount: with pool.checkout() as session: contents = session.feed_run("open('/data/notes.txt').read()", mount=mount)

类型检查:ty 集成

Monty 内置了 ty 类型检查器:每个 feed 的代码片段可在 worker 内、运行之前被类型检查,成功执行的片段会累积进检查上下文,用于后续片段。这在 AI 场景中尤其有价值——在把代码送进沙箱跑之前先拦截类型错误。

from pydantic_monty import Monty, MontyTypingError with Monty() as pool: with pool.checkout(type_check=True) as session: try: session.feed_run("x: int = 'not an int'") except MontyTypingError as exc: print('invalid-assignment' in exc.display()) #> True

type_check_format选择诊断的渲染格式,取值即 ty 的'full'(默认:源码片段加脱字符)、'concise''azure''json''jsonlines''rdjson''pylint''gitlab''github'type_check_color'full'/'concise'追加 ANSI 颜色。二者都是checkout()参数而非display()参数,原因见 crates/monty-python/python/pydantic_monty/init.py 的类型别名文档:类型检查器在 worker 内部运行,其结构化诊断永不离开 worker(ty 的结构化诊断需对照检查器数据库解析 span),跨线传输的只有已渲染的文本。

from pydantic_monty import Monty, MontyTypingError with Monty() as pool: with pool.checkout(type_check=True, type_check_format='concise') as session: try: session.feed_run("x: int = 'not an int'") except MontyTypingError as exc: print(exc.display()) """ main.py:1:10: error[invalid-assignment] Object of type `Literal["not an int"]` is not assignable to `int` """

type_check_stubs可为类型检查提供额外的 stub 声明;feed_run(..., skip_type_check=True)可在会话开启类型检查时跳过单次 feed。

崩溃与故障隔离

Monty 代码执行中的一切失败都抛MontyError的子类。异常层级(定义于 crates/monty-python/python/pydantic_monty/_monty.pyi)包括:

  • MontySyntaxError:语法错误或无法解析;
  • MontyRuntimeError:执行期失败,携带traceback()Frame列表,含文件名、1 起始行列号、函数名、源码行)与display(format=...)'traceback'/'type-msg'/'msg'三种格式);
  • MontyTypingError:类型检查拒绝(诊断按 checkout 时选择的格式预渲染好);
  • MontyConversionError:宿主值无法跨边界转换(如external_lookup/inputs中出现不支持的类型的值),feed 被拒绝;
  • MontyCrashedErrorworker 进程消失——段错误、分配器 abort、外部杀死、request_timeout看门狗,或它在退出前宣告的致命错误;宿主进程毫发无损,池会替换 worker。timed_out属性指示是否为看门狗所致,exit_status给出 OS 报告的退出码(信号死亡时为None,远程 worker 恒为None);
  • MontyDisconnectError(仅 WebSocket 传输):远程 worker 连接在会话中途关闭——可能是沙箱死了,也可能是服务端按策略(空闲/会话/轮次超时、过载)断开会话,客户端无法区分,应在新会话重试;
  • MontyShutdown(仅 WebSocket 传输):远程服务器正在关停;该请求没有运行,可在新会话安全重跑,且其dump携带关停前捕获的会话状态,可用load_session/load_snapshot跨服务器重启续上会话(注意:若被中断的请求正在应答某个挂起,宿主已执行过该回调,恢复后会重新宣布并再次执行,此类回调应保持幂等)。
from pydantic_monty import Monty, MontyError hostile_code = '...' with Monty() as pool: with pool.checkout() as session: try: session.feed_run(hostile_code) # 即使是段错误也被隔离 except MontyError: ... # worker 已死;池已经替换了它

可观测性:OpenTelemetry 集成

先安装可选支持,再在创建任何池之前用标准的 Python OpenTelemetry 组件调用instrument_telemetry

from opentelemetry import _logs, metrics, trace from pydantic_monty import instrument_telemetry instrument_telemetry( tracer=trace.get_tracer('pydantic-monty'), meter=metrics.get_meter('pydantic-monty'), logger=_logs.get_logger('pydantic-monty'), )

每个组件都是可选的,安装是进程级且只能发生一次,各信号可独立启用:

  • Tracer:把每次 checkout 记录为 session span,嵌套 feed span 与挂起(suspension)span;
  • Logger:在这些 span 下记录异常与print输出;
  • WebSocketAsyncMontyWebsocket的 checkout 还会在升级请求上发送当前上下文为 W3Ctraceparent/tracestate头,支持的服务端可加入同一 trace;connect_headers回调(每个会话进入时、等待池容量之前调用一次,同步运行于 checkout 任务上,可见其 contextvars,不可阻塞)可注入额外的strstr头,如基础设施令牌,重复名后者胜出;
  • Meter:记录实时、立即可用与宿主阻塞的 worker 数,checkout 等待时长,按原因的 worker 死亡数,运行时长,以及每次 feed 的沙箱执行时间。

两个安全设计值得强调(见 crates/monty-python/python/pydantic_monty/init.py 与 README 的 Observability 一节):

  1. 指标覆盖每次 checkout,但不记录任何沙箱提供的值:其属性是封闭集合,脚本选择的任何内容(被调用函数名、异常类、路径)都不会成为维度——防止沙箱数据污染监控维度;
  2. trace 与 log 会记录代码、输入、外部调用、异常与打印输出;会话 dump 与恢复只按大小记录。在instrument_telemetry被调用之前插桩处于禁用状态,启用后的大值会在遥测属性大小限制处截断。

供应商无关是刻意设计:提供的 OpenTelemetry providers 拥有 ID、采样、metric views 与聚合、resources、readers、exporters、flush 与 shutdown 的自主权,因此 Logfire 等 OpenTelemetry 发行版可走同一条插桩路径(如logfire.instrument_monty()提供绑定到其配置实例的组件)。

延伸阅读

  • docs/host-objects.md:宿主对象/类跨边界机制的完整设计文档
  • docs/cli.md:monty二进制命令行参考
  • docs/resource-limits.md 与 limitations/resource_limits.md:资源限制的详细行为
  • limitations/pool-architecture.md:子进程执行的架构细节(宿主侧挂载、缓冲 print 回调、会话 dump)
  • crates/monty-python/tests:本仓库的 Python 绑定测试(test_limits.pytest_type_check.pytest_telemetry.pytest_feed_start.py等),可当作活文档阅读
  • crates/monty-python/python/pydantic_monty/_monty.pyi:全部公开 API 的完整类型签名与逐参数说明

【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:02:04

AI出海实战:算力部署与生态协同落地指南

1. 这不是一场技术发布会&#xff0c;而是一次出海实操复盘“2025-2026年中国AI出海”——这八个字最近在不少技术团队晨会、投资人尽调清单和跨境SaaS产品路线图里高频出现。但说实话&#xff0c;我去年底在新加坡一家本地银行做POC时&#xff0c;客户CTO盯着我们模型API响应延…

作者头像 李华
网站建设 2026/9/16 18:00:59

银行流水数据分析系统:从数据清洗到异常检测实战

简介&#xff1a;一套基于前后端分离架构的银行流水数据分析系统毕设项目&#xff0c;面向计算机相关专业学生及数据分析入门者&#xff0c;为解决单一账户多笔资金流向处理与展示的人工筛查问题提供参考。压缩包共56个文件&#xff0c;以21个Go后端文件、12个Vue前端文件和10个…

作者头像 李华
网站建设 2026/9/16 18:00:58

Mac重启机制与内存清理深度解析

1. Mac关机重启的清理机制解析当我们在Mac上点击"重启"按钮时&#xff0c;系统会执行一系列底层清理操作。这个过程远比普通用户想象的要复杂得多&#xff0c;它实际上触发了Unix内核级别的内存管理机制。1.1 内存释放的真实过程MacOS基于Unix的进程管理机制会在关机…

作者头像 李华
网站建设 2026/9/16 17:58:42

FPGA万年历数字时钟设计:时序精度与硬件建模实战

简介&#xff1a;本资源是一套完整的FPGA万年历数字时钟课程设计实践材料&#xff0c;面向电子类、通信类及计算机工程专业本科生与初学者&#xff0c;解决数字系统设计中时序逻辑、人机交互与模块化开发等核心教学难点。压缩包含406个文件&#xff0c;以68个.cdb&#xff08;编…

作者头像 李华