
مترجم بايثون بسيط وآمن مكتوب بلغة Rust لاستخدامه من قبل الذكاء الاصطناعي
تجريبي - هذا المشروع لا يزال قيد التطوير، وغير جاهز بعد للاستخدام الفعلي.
مفسّر بايثون بسيط وآمن مكتوب بلغة Rust ليُستخدم بواسطة الذكاء الاصطناعي.
يتجنّب Monty التكلفة وزمن الاستجابة والتعقيد والمتاعب العامة لاستخدام بيئة حماية (sandbox) كاملة قائمة على الحاويات لتشغيل الكود المولّد بواسطة نماذج اللغة الكبيرة (LLM).
بدلاً من ذلك، يتيح لك تشغيل كود Python المكتوب بواسطة نموذج لغوي كبير (LLM) مدمج في وكيلك بأمان، مع زمن بدء تشغيل يُقاس بوحدات أحادية من الميكروثانية وليس بمئات الميلي ثانية.
ما يمكن أن يفعله Monty:
sys, os, typing, asyncio, re, datetime, json, dataclasses (قريبًا)ما لا يمكن أن يفعله Monty:
باختصار، Monty محدود للغاية ومصمَّم لحالة استخدام واحدة:
لتشغيل الكود المكتوب بواسطة الوكلاء.
للتعرّف على الدافع وراء رغبتك في القيام بذلك، انظر:
بعبارات بسيطة جدًا، الفكرة من كل ما سبق هي أن نماذج اللغة الكبيرة (LLMs) يمكنها العمل بشكل أسرع وأرخص وأكثر موثوقية إذا طُلب منها كتابة كود Python (أو Javascript) بدلاً من الاعتماد على استدعاء الأدوات التقليدي. يجعل Monty ذلك ممكنًا دون تعقيد بيئة الحماية (sandbox) أو مخاطرة تشغيل الكود مباشرة على المضيف.
ملاحظة: سوف يُستخدم Monty (قريبًا) لتنفيذ codemode في Pydantic AI
يمكن استدعاء Monty من Python أو JavaScript/TypeScript أو Rust.
للتثبيت:```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())
هناك عمومًا ردّان عندما تعرض Monty على الأشخاص:
حيث X هي تقنية بديلة ما. ومن الغريب أن هذه الردود كثيرًا ما تأتي مجتمعة، مما يوحي بأن الأشخاص لم يعثروا بعد على بديل يناسبهم، لكنهم لا يصدقون أنه لا يوجد بديل جيد حقًا لإنشاء تنفيذ كامل للغة Python من الصفر.
سأحاول استعراض البدائل الأكثر وضوحًا، ولماذا لم تكن مناسبة لما أردناه.
ملاحظة: كل هذه التقنيات مذهلة ولها استخدامات واسعة النطاق، ولا ينبغي النظر إلى هذا التعليق حول قيودها بالنسبة لحالة الاستخدام لدينا على أنه انتقاد. معظم هذه الحلول لم تُصمَّم بهدف توفير بيئة معزولة (sandbox) لنماذج اللغة الكبيرة (LLM)، ولهذا السبب فهي ليست بالضرورة رائعة في ذلك.
انظر ./scripts/startup_performance.py لمعرفة البرنامج النصي المستخدم لحساب أرقام أداء بدء التشغيل.
تفاصيل كل صف أدناه:
pip install pydantic-monty أو npm install @pydantic/monty، حمولة تنزيل تبلغ ~4.5MBdump() و load() تجعل من السهل جدًا إيقاف التنفيذ واستئنافه وتفرّعه (fork)python:3.14-alpine حجمها 50MB - لا يمكن تثبيت docker من PyPIانظر starlark-rust.
تشغيل Python في WebAssembly عبر Wasmer.
python/python wasmer لا تحتوي على readme ولا ترخيص ولا رابط للمصدر ولا أي إشارة إلى طريقة بنائها، والإصدارات المرفوعة مؤخرًا تُظهر الحجم على أنه "0B" رغم أن حمولة التنزيل تبلغ ~50MB - عملية بناء ثنائي Python ليست واضحة وشفافة. (إذا كنت مخطئًا هنا، فيرجى إنشاء issue لتصحيحي)خدمات مثل Daytona وE2B وModal.
هناك تحديات مماثلة، تعقيد إعداد أكبر لكن زمن استجابة شبكة أقل لإعداد بيئة العزل الخاصة بك مع k8s.
تشغيل Python مباشرة عبر exec() (~0.1ms) أو عبر subprocess (~30ms).
exec()، و~30ms مع subprocessPydantic Stack هو كل ما تحتاجه لإطلاق وكلاء ذكاء اصطناعي بجودة الإنتاج:
| التقنية | اكتمال اللغة | الأمان | زمن بدء التشغيل | مفتوح المصدر (FOSS) | تعقيد الإعداد | تركيب الملفات | التقاط اللقطات |
|---|
| Monty | جزئي | صارم | 0.06ms | مجاني / مفتوح المصدر | سهل | سهل | سهل |
| Docker | كامل | جيد | 195ms | مجاني / مفتوح المصدر | متوسط | سهل | متوسط |
| Pyodide | كامل | ضعيف | 2800ms | مجاني / مفتوح المصدر | متوسط | سهل | صعب |
| starlark-rust | محدود جدًا | جيد | 1.7ms | مجاني / مفتوح المصدر | سهل | غير متاح؟ | مستحيل؟ |
| WASI / Wasmer | جزئي، شبه كامل | صارم | 66ms | مجاني * | متوسط | سهل | متوسط |
| sandboxing service | كامل | صارم | 1033ms | غير مجاني | متوسط | صعب | متوسط |
| YOLO Python | كامل | غير موجود | 0.1ms / 30ms | مجاني / مفتوح المصدر | سهل | سهل / مخيف | صعب |