
monty v0.0.19
مترجم بايثون بسيط وآمن مكتوب بلغة Rust لاستخدامه من قبل الذكاء الاصطناعي
Monty
مفسّر بايثون بسيط وآمن مكتوب بلغة Rust ليُستخدم بواسطة الذكاء الاصطناعي.
تجريبي - هذا المشروع لا يزال قيد التطوير، وغير جاهز بعد للاستخدام الفعلي.
مفسّر بايثون بسيط وآمن مكتوب بلغة Rust ليُستخدم بواسطة الذكاء الاصطناعي.
يتجنّب Monty التكلفة وزمن الاستجابة والتعقيد والمتاعب العامة لاستخدام بيئة حماية (sandbox) كاملة قائمة على الحاويات لتشغيل الكود المولّد بواسطة نماذج اللغة الكبيرة (LLM).
بدلاً من ذلك، يتيح لك تشغيل كود Python المكتوب بواسطة نموذج لغوي كبير (LLM) مدمج في وكيلك بأمان، مع زمن بدء تشغيل يُقاس بوحدات أحادية من الميكروثانية وليس بمئات الميلي ثانية.
ما يمكن أن يفعله Monty:
- تشغيل مجموعة فرعية معقولة من أكواد Python - بما يكفي لوكيلك للتعبير عمّا يريد فعله
- منع الوصول تمامًا إلى بيئة المضيف: الوصول إلى نظام الملفات ومتغيرات البيئة والشبكة كلها منفّذة عبر استدعاءات دوال خارجية يمكن للمطوّر التحكم بها
- استدعاء الدوال على المضيف - فقط الدوال التي تمنحه إمكانية الوصول إليها
- تشغيل فحص الأنواع - يدعم Monty تلميحات الأنواع الحديثة الكاملة في Python ويأتي مع ty مضمّنًا في ملف ثنائي واحد لتشغيل فحص الأنواع
- إمكانية أخذ لقطة (snapshot) منه إلى بايتات عند استدعاءات الدوال الخارجية، ما يعني أنه يمكنك تخزين حالة المفسّر في ملف أو قاعدة بيانات واستئناف العمل لاحقًا
- بدء تشغيل سريع للغاية (<1μs من الكود إلى نتيجة التنفيذ)، وأداء وقت تشغيل مشابه لأداء CPython (بشكل عام بين أسرع بخمس مرات وأبطأ بخمس مرات)
- إمكانية استدعائه من Rust أو Python أو Javascript - ولأن Monty لا يعتمد على cpython، يمكنك استخدامه في أي مكان يمكنك فيه تشغيل Rust
- التحكم في استخدام الموارد - يمكن لـ Monty تتبّع استخدام الذاكرة وعمق المكدّس وزمن التنفيذ وإلغاء التنفيذ إذا تجاوز الحدود المحددة مسبقًا
- جمع stdout و stderr وإرجاعهما إلى المتصل
- تشغيل كود غير متزامن (async) أو متزامن (sync) على المضيف عبر كود غير متزامن (async) أو متزامن (sync) على المضيف
- استخدام مجموعة فرعية صغيرة من المكتبة القياسية:
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
بعبارات بسيطة جدًا، الفكرة من كل ما سبق هي أن نماذج اللغة الكبيرة (LLMs) يمكنها العمل بشكل أسرع وأرخص وأكثر موثوقية إذا طُلب منها كتابة كود Python (أو Javascript) بدلاً من الاعتماد على استدعاء الأدوات التقليدي. يجعل Monty ذلك ممكنًا دون تعقيد بيئة الحماية (sandbox) أو مخاطرة تشغيل الكود مباشرة على المضيف.
ملاحظة: سوف يُستخدم Monty (قريبًا) لتنفيذ codemode في Pydantic AI
الاستخدام
يمكن استدعاء 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، ويُستبدل. هناك أيضًا واجهة برمجة تطبيقات متزامنة بالكامل:```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 هي ربط أصلي (napi) فوق نفس تجمّع عمال Rust الذي تستخدمه حزمة Python — حيث يُشحن الربط والملف الثنائي للعامل 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' }, })
بالنسبة للمتصفحات (أو أي مكان يتعذّر فيه استخدام العمليات الفرعية)، تُتيح الحزمة نفسها بناءً على WebAssembly داخل العملية عبر المسار الفرعي `@pydantic/monty/wasm` (بدون عزل الانهيارات: انهيار بيئة الحماية هو انهيار للمضيف هناك).
### Rust
لتشغيل تعليمات برمجية غير موثوقة من Rust، نوصي باستخدام صندوق
[`monty-pool`](https://crates.io/crates/monty-pool) بدلاً من واجهة البرمجة داخل العملية أدناه.
يعمل `monty-pool` حصريًا داخل عمليات العمال الفرعية `monty`، مما يوفر حمايات إضافية:
الانهيار الناتج عن تعليمات برمجية خصومة (تجاوز سعة المكدس، إحباط المخصّص) يقتل العامل فقط —
تكتشف المجموعة الوفاة وتستبدل العامل — ويمكن لمراقب خارجي على جانب الأصل قتل العمال
الذين يتجاوزون حد المهلة الصارم. إنه نفس المحرك الذي تستند إليه حزمتا Python وJavaScript أعلاه.
راجع [ملف README الخاص بـ monty-pool](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};
// Snapshot a session between snippets let mut repl = MontyRepl::new("main.py", ResourceTracker::default(), CompileOptions::default()); repl.feed_run("x = 41", vec![], PrintWriter::Stdout).unwrap(); let bytes = dump("main.py", None, SessionRef::Idle(&repl)).unwrap();
// Later, restore and carry on feeding let Session::Idle(mut restored) = Dump::load(&bytes).unwrap().state else { panic!("dumped an idle session") }; let result = restored.feed_run("x + 1", vec![], PrintWriter::Stdout).unwrap(); assert_eq!(result, MontyObject::Int(42));
`MontyRun` و`RunProgress` ليس لهما تنسيق تفريغ خاص بهما، لكن كلاهما يطبّق `serde::Serialize`/`Deserialize`، لذا يمكن للمضيف تسلسل الكود المُحلَّل أو تشغيلًا متوقفًا مؤقتًا بأي تنسيق يستخدمه بالفعل.
## حدود الذاكرة في العمال
يتم قياس `max_memory` للجلسة بواسطة مُخصِّص الذاكرة الخاص بالعامل. يبلّغ المفسِّر عن `MemoryError` بشكل سلس بعد تجاوز الحد المرن؛ أما الحد الصارم الأعلى فيُنهي العامل ويستبدله إذا قفز أحد التخصيصات بعيدًا جدًا بين نقاط التفتيش.
انظر [`limitations/resource_limits.md`](https://github.com/pydantic/monty/blob/HEAD/limitations/resource_limits.md) لمعرفة كيفية ظهور تجاوز الحد للمضيف، و`monty-alloc` للمُخصِّص الذي تعمل تحته كلٌّ من عمال العملية الفرعية وعمال WebAssembly.
## تكامل PydanticAI
سيعمل Monty على تشغيل code-mode في [Pydantic AI](https://github.com/pydantic/pydantic-ai). بدلاً من إجراء استدعاءات أدوات متسلسلة، يكتب نموذج اللغة الكبير (LLM) كود Python يستدعي أدواتك كدوال، وينفّذه Monty بأمان.```python test="skip"
import asyncio
import json
import logfire
from httpx import AsyncClient
from pydantic_ai import Agent, RunContext
from pydantic_ai.toolsets.code_mode import CodeModeToolset
from pydantic_ai.toolsets.function import FunctionToolset
from typing_extensions import TypedDict
logfire.configure()
logfire.instrument_pydantic_ai()
class LatLng(TypedDict):
lat: float
lng: float
weather_toolset: FunctionToolset[AsyncClient] = FunctionToolset()
@weather_toolset.tool
async def get_lat_lng(
ctx: RunContext[AsyncClient], location_description: str
) -> LatLng:
"""Get the latitude and longitude of a location."""
# NOTE: the response here will be random, and is not related to the location description.
r = await ctx.deps.get(
'https://demo-endpoints.pydantic.workers.dev/latlng',
params={'location': location_description},
)
r.raise_for_status()
return json.loads(r.content)
@weather_toolset.tool
async def get_temp(ctx: RunContext[AsyncClient], lat: float, lng: float) -> float:
"""Get the temp at a location."""
# NOTE: the responses here will be random, and are not related to the lat and lng.
r = await ctx.deps.get(
'https://demo-endpoints.pydantic.workers.dev/number',
params={'min': 10, 'max': 30},
)
r.raise_for_status()
return float(r.text)
@weather_toolset.tool
async def get_weather_description(
ctx: RunContext[AsyncClient], lat: float, lng: float
) -> str:
"""Get the weather description at a location."""
# NOTE: the responses here will be random, and are not related to the lat and lng.
r = await ctx.deps.get(
'https://demo-endpoints.pydantic.workers.dev/weather',
params={'lat': lat, 'lng': lng},
)
r.raise_for_status()
return r.text
agent = Agent(
'gateway/anthropic:claude-sonnet-4-5',
# toolsets=[weather_toolset],
toolsets=[CodeModeToolset(weather_toolset)],
deps_type=AsyncClient,
)
async def main():
async with AsyncClient() as client:
await agent.run('Compare the weather of London, Paris, and Tokyo.', deps=client)
if __name__ == '__main__':
asyncio.run(main())
روابط المجتمع
- Go: gomonty - روابط Go لمفسّر Monty
- Dart/Flutter: dart_monty (github) (pub.dev)- روابط Dart/Flutter لـ Monty
البدائل
هناك عمومًا ردّان عندما تعرض Monty على الأشخاص:
- يا إلهي، هذا يحل الكثير من المشاكل، أريده.
- لماذا لا نستخدم X؟
حيث X هي تقنية بديلة ما. ومن الغريب أن هذه الردود كثيرًا ما تأتي مجتمعة، مما يوحي بأن الأشخاص لم يعثروا بعد على بديل يناسبهم، لكنهم لا يصدقون أنه لا يوجد بديل جيد حقًا لإنشاء تنفيذ كامل للغة Python من الصفر.
سأحاول استعراض البدائل الأكثر وضوحًا، ولماذا لم تكن مناسبة لما أردناه.
ملاحظة: كل هذه التقنيات مذهلة ولها استخدامات واسعة النطاق، ولا ينبغي النظر إلى هذا التعليق حول قيودها بالنسبة لحالة الاستخدام لدينا على أنه انتقاد. معظم هذه الحلول لم تُصمَّم بهدف توفير بيئة معزولة (sandbox) لنماذج اللغة الكبيرة (LLM)، ولهذا السبب فهي ليست بالضرورة رائعة في ذلك.
| التقنية | اكتمال اللغة | الأمان | زمن بدء التشغيل | مفتوح المصدر (FOSS) | تعقيد الإعداد | تركيب الملفات | التقاط اللقطات |
|---|---|---|---|---|---|---|---|
| Monty | جزئي | صارم | 0.06ms | مجاني / مفتوح المصدر | سهل | سهل | سهل |
| Docker | كامل | جيد | 195ms | مجاني / مفتوح المصدر | متوسط | سهل | متوسط |
| Pyodide | كامل | ضعيف | 2800ms | مجاني / مفتوح المصدر | متوسط | سهل | صعب |
| starlark-rust | محدود جدًا | جيد | 1.7ms | مجاني / مفتوح المصدر | سهل | غير متاح؟ | مستحيل؟ |
| WASI / Wasmer | جزئي، شبه كامل | صارم | 66ms | مجاني * | متوسط | سهل | متوسط |
| sandboxing service | كامل | صارم | 1033ms | غير مجاني | متوسط | صعب | متوسط |
| YOLO Python | كامل | غير موجود | 0.1ms / 30ms | مجاني / مفتوح المصدر | سهل | سهل / مخيف | صعب |
انظر ./scripts/startup_performance.py لمعرفة البرنامج النصي المستخدم لحساب أرقام أداء بدء التشغيل.
تفاصيل كل صف أدناه:
Monty
- اكتمال اللغة: لا توجد فئات (حتى الآن)، مكتبة قياسية محدودة، لا توجد مكتبات خارجية
- الأمان: وصول مُتحكَّم فيه صراحةً لنظام الملفات والشبكة والمتغيرات البيئية، حدود صارمة على زمن التنفيذ واستخدام الذاكرة
- زمن بدء التشغيل: يبدأ خلال ميكروثانية
- تعقيد الإعداد: فقط
pip install pydantic-montyأوnpm install @pydantic/monty، حمولة تنزيل تبلغ ~4.5MB - تركيب الملفات: مُتحكَّم فيه بدقة، انظر #85
- التقاط اللقطات: وظيفة الإيقاف والاستئناف في Monty عبر
dump()وload()تجعل من السهل جدًا إيقاف التنفيذ واستئنافه وتفرّعه (fork)
Docker
- اكتمال اللغة: CPython كامل مع أي مكتبة
- الأمان: عزل للعمليات ونظام الملفات، وسياسات شبكة، لكن توجد ثغرات هروب من الحاويات، وإمكانية تحديد الذاكرة موجودة
- زمن بدء التشغيل: حمل إضافي لبدء تشغيل الحاوية (بقياس ~195ms)
- تعقيد الإعداد: يتطلب Docker daemon وصور حاويات وتنسيقًا (orchestration)،
python:3.14-alpineحجمها 50MB - لا يمكن تثبيت docker من PyPI - تركيب الملفات: تركيبات وحدات التخزين (volume mounts) تعمل جيدًا
- التقاط اللقطات: ممكن عبر حلول التنفيذ الدائم (durable execution) مثل Temporal، أو التقاط لقطة لصورة وحفظها كصورة Docker.
Pyodide
- اكتمال اللغة: CPython كامل مُصرَّف إلى WASM، وتتوفر تقريبًا جميع المكتبات
- الأمان: يعتمد على بيئة العزل في المتصفح/WASM - غير مصمَّم للعزل من جهة الخادم، ويمكن لرمز Python تشغيل أي رمز في بيئة JS وقت التشغيل، وdeno فقط تتيح العزل، وتطبيق حدود الذاكرة مع deno صعب/مستحيل
- زمن بدء التشغيل: تحميل بيئة WASM الزمنية بطيء (~2800ms بدء بارد)
- تعقيد الإعداد: تحتاج إلى تحميل بيئة WASM الزمنية والتعامل مع التهيئة غير المتزامنة، حزمة pyodide على NPM تبلغ ~12MB، وdeno تبلغ ~50MB - لا يمكن استدعاء Pyodide بحزم PyPI فقط
- تركيب الملفات: نظام ملفات افتراضي عبر واجهات المتصفح البرمجية (APIs)
- التقاط اللقطات: ممكن مع حلول التنفيذ الدائم مثل Temporal على الأرجح، لكنه صعب
starlark-rust
انظر starlark-rust.
- اكتمال اللغة: لغة إعدادات (configuration) وليست Python - لا توجد فئات أو استثناءات أو برمجة غير متزامنة (async)
- الأمان: حتمية (deterministic) ومعزولة (hermetic) بالتصميم
- زمن بدء التشغيل: يعمل مضمّنًا داخل العملية مثل Monty، ومن هنا يأتي زمن بدء التشغيل المذهل
- تعقيد الإعداد: يمكن استخدامه في Python عبر starlark-pyo3
- تركيب الملفات: لا معالجة للملفات بالتصميم على حد علمي (AFAIK)؟
- التقاط اللقطات: مستحيل على حد علمي (AFAIK)؟
WASI / Wasmer
تشغيل Python في WebAssembly عبر Wasmer.
- اكتمال اللغة: CPython كامل، الحزم الخارجية النقية Python تعمل عبر التركيب (mounting)، أما الحزم الخارجية ذات الروابط C فلا تعمل
- الأمان: من حيث المبدأ، ينبغي أن يوفر WebAssembly ضمانات عزل قوية.
- زمن بدء التشغيل: حزمة wasmer لـ Python لم تُحدَّث منذ 3 سنوات، ولم أتمكن من العثور على وثائق حول استدعاء Python داخل wasmer من Python، لذلك استدعيتها عبر subprocess. كان زمن بدء التشغيل 66ms.
- تعقيد الإعداد: حمولة تنزيل wasmer تبلغ 100mb، وحزمة "python/python" تبلغ 50mb.
- مفتوح المصدر (FOSS): وضعت هذا الخيار على أنه "مجاني *" لأن التكلفة صفر، لكن ليس كل شيء يبدو مفتوح المصدر. اعتبارًا من 2026-02-10، حزمة
python/pythonwasmer لا تحتوي على readme ولا ترخيص ولا رابط للمصدر ولا أي إشارة إلى طريقة بنائها، والإصدارات المرفوعة مؤخرًا تُظهر الحجم على أنه "0B" رغم أن حمولة التنزيل تبلغ ~50MB - عملية بناء ثنائي Python ليست واضحة وشفافة. (إذا كنت مخطئًا هنا، فيرجى إنشاء issue لتصحيحي) - تركيب الملفات: مدعوم
- التقاط اللقطات: مدعوم عبر التسجيل (journaling)
خدمة العزل (sandboxing)
خدمات مثل Daytona وE2B وModal.
هناك تحديات مماثلة، تعقيد إعداد أكبر لكن زمن استجابة شبكة أقل لإعداد بيئة العزل الخاصة بك مع k8s.
- اكتمال اللغة: CPython كامل مع أي مكتبة
- الأمان: عزل حاويات مُدار باحترافية
- زمن بدء التشغيل: زمن الرحلة ذهابًا وإيابًا عبر الشبكة وزمن بدء تشغيل الحاوية. حصلت على زمن بدء بارد ~1s مع Daytona EU من لندن، وتعلن Daytona عن زمن استجابة أقل من 90ms، ومن المفترض أن ذلك لحاوية قائمة بالفعل، وليس واضحًا ما إذا كان يشمل زمن استجابة الشبكة
- مفتوح المصدر (FOSS): الدفع مقابل كل تنفيذ أو وقت حوسبة، بعض التطبيقات مفتوحة المصدر
- تعقيد الإعداد: تكامل عبر API ورموز مصادقة (auth tokens) - مناسب للشركات الناشئة لكنه عمومًا غير وارد للمؤسسات الكبيرة
- تركيب الملفات: رفع/تنزيل عبر استدعاءات API
- التقاط اللقطات: ممكن مع حلول التنفيذ الدائم مثل Temporal، وتقدم الخدمات أيضًا بعض الحلول لهذا، أعتقد أنها مبنية على حاويات docker
YOLO Python
تشغيل Python مباشرة عبر exec() (~0.1ms) أو عبر subprocess (~30ms).
- اكتمال اللغة: CPython كامل مع أي مكتبة
- الأمان: لا يوجد - وصول كامل لنظام الملفات والشبكة والمتغيرات البيئية وأوامر النظام
- زمن بدء التشغيل: شبه صفر مع
exec()، و~30ms مع subprocess - تعقيد الإعداد: لا يوجد
- تركيب الملفات: وصول مباشر لنظام الملفات (وهذه هي المشكلة)
- التقاط اللقطات: ممكن مع حلول التنفيذ الدائم مثل Temporal
جزء من Pydantic Stack
Pydantic Stack هو كل ما تحتاجه لإطلاق وكلاء ذكاء اصطناعي بجودة الإنتاج:
- Pydantic AI - إطار عمل وكلاء آمن الأنواع (Type-safe)
- Pydantic Logfire - مراقبة (observability) كاملة السلسلة بأولوية الذكاء الاصطناعي
- Logfire AI Gateway - وكيل (proxy) موحّد لنماذج LLM