Python कोड निष्पादन (Monty)

ADK-Rust मॉडल द्वारा लिखे गए Python कोड को Pydantic Monty इंटरप्रेटर के माध्यम से in-process चलाता है — किसी कंटेनर या सबप्रोसेस के बिना, और माइक्रोसेकंड में स्टार्टअप। यह क्षमता दो स्तरों में उपलब्ध है:

  • adk-code (embedded-python सुविधा) — MontyExecutorBuilder और दो executor उत्पाद, MontyOneShotExecutor और MontyReplExecutor, दोनों CodeExecutor को लागू करते हैं।
  • adk-tool (code-embedded-python सुविधा) — MontyPythonCodeTool (monty_python_code), उन executors पर agent-केंद्रित tool।

यह कंटेनर-आधारित PythonCodeTool (python_code) का पूरक है, जो Docker में पूर्ण CPython चलाता है — जब स्क्रिप्ट को वास्तविक Python इकोसिस्टम (pip पैकेज, C एक्सटेंशन, पूर्ण standard library) की आवश्यकता हो, तब उसका उपयोग करें। Monty Python के एक उपसमुच्चय को लागू करता है और इसके बदले in-process गति, क्रमबद्ध किए जा सकने वाली इंटरप्रेटर स्थिति, तथा ऐसा no-network/no-subprocess गारंटी देता है जो संरचना के आधार पर सुनिश्चित होती है।

[dependencies]
adk-tool = { version = "2.1.0", features = ["code-embedded-python"] }

या umbrella crate के माध्यम से:

[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "code-embedded-python"] }

One-shot बनाम REPL

एक builder दोनों उत्पाद तैयार करता है; mode को flag में नहीं, बल्कि type में एन्कोड किया जाता है:

मोडबिल्डस्थितिसमवर्ती निष्पादन
एकल-प्रयोगbuild_one_shot()प्रत्येक कॉल के लिए नया इंटरप्रेटरसमवर्ती निष्पादन-सुरक्षित
REPLbuild_repl()वेरिएबल, फ़ंक्शन और इम्पोर्ट कॉल के बीच बने रहते हैंप्रत्येक सत्र में कॉल क्रमबद्ध होते हैं
use adk_code::{MontyExecutorBuilder, PathAccess};

let builder = MontyExecutorBuilder::new()
    .allow_path("/data", "/srv/agent/data", PathAccess::ReadOnly)
    .allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
    .environ_var("PROJECT", "acme")
    .system_clock();

let one_shot = builder.clone().build_one_shot()?;
let repl = builder.build_repl()?;

REPL निष्पादक कॉल के बीच क्रमबद्ध इंटरप्रेटर को संग्रहीत करता है। Monty Python-स्तरीय अपवादों के माध्यम से सत्र को बनाए रखता है, इसलिए विफल स्निपेट संचित स्थिति को नष्ट नहीं करता। CodeExecutor जीवनचक्र विधियाँ सत्र का प्रबंधन करती हैं: start() इसे प्रारंभ करता है, stop() इसे हटाता है, restart() इसे रीसेट करता है, और execute(), start() से पहले, इसे आलस्यपूर्वक प्रारंभ करता है।

सुरक्षा मॉडल

पृथक्करण स्पष्ट नीति को विलोपन द्वारा प्रवर्तन के साथ संयोजित करता है:

  • फ़ाइल सिस्टम। केवल allow_path के साथ अनुमत निर्देशिकाएँ ही पहुँच योग्य हैं, प्रत्येक केवल-पठन या पठन-लेखन के रूप में, आभासी माउंट पथ के विरुद्ध pathlib.Path के माध्यम से। Monty की माउंट तालिका सीमा लागू करती है (कैनोनिकलाइज़ेशन + सिमलिंक-एस्केप पहचान)। कोई भी अन्य पथ पकड़ने योग्य OSError उत्पन्न करता है (अस्तित्व जाँचें False लौटाती हैं)।
  • पर्यावरण। os.getenv / os.environ केवल निर्माण के समय अनुमत स्पष्ट मानचित्र को पढ़ते हैं — होस्ट प्रक्रिया का पर्यावरण कभी उजागर नहीं होता।
  • घड़ी। date.today() / datetime.now() केवल तभी कार्य करते हैं जब .system_clock() की अनुमति दी गई हो; अन्यथा वे OSError उत्पन्न करते हैं।
  • नेटवर्क और सबप्रोसेस। Monty में दोनों के लिए कोई सतह नहीं है — विन्यास की परवाह किए बिना यह असंभव है।
  • समय-सीमाएँ। SandboxPolicy::timeout Monty के ResourceLimits::max_duration पर मैप करता है (प्रति कॉल वास्तविक VM-पूर्व-अवरोधन)। एक मेमोरी सीमा (डिफ़ॉल्ट 256 MiB) हीप को सीमित करती है; REPL मोड में यह संचयी सत्र हीप को सीमित करती है।

अनुदान बनाम अनुरोध नीति। बिल्डर के अनुदान किसी भी स्क्रिप्ट की अधिकतम पहुँच निर्धारित करते हैं। प्रति-अनुरोध SandboxPolicy इन सीमाओं के भीतर ही पहुँच को कम कर सकता है — अनुदान से अधिक पहुँच का अनुरोध fail-closed तरीके से अस्वीकार कर दिया जाता है और किसी भी कोड के चलने से पहले अधिकता का नाम देने वाला ExecutionError::UnsupportedPolicy लौटाया जाता है। कोई अनुदान अपनी पूरी डायरेक्टरी सबट्री को कवर करता है: किसी दिए गए माउंट का अनुरोध या उसके किसी सबडायरेक्टरी का अनुरोध सफल होता है, और प्रभावी माउंट अनुरोधित पथ होता है, जिसके पीछे संबंधित होस्ट सबडायरेक्टरी होती है। executor द्वारा उपलब्ध कराई गई चीज़ का ठीक-ठीक अनुरोध करने के लिए granted_policy() का उपयोग करें।

REPL सत्र की प्रभावी नीति कॉल के बीच परिवर्तित नहीं होनी चाहिए; जिस कॉल की नीति सत्र के स्थापित नीति से भिन्न होती है, उसे restart() के लिए मार्गदर्शन के साथ अस्वीकार कर दिया जाता है।

होस्ट फ़ंक्शन

पंजीकृत Rust फ़ंक्शन (sync या async) कॉल किए जा सकने वाले Python फ़ंक्शन बन जाते हैं, जो स्क्रिप्ट के लिए सीधे नाम से दिखाई देते हैं:

use adk_code::MontyExecutorBuilder;
use serde_json::json;

let executor = MontyExecutorBuilder::new()
    .function_fn("row_count", "Count rows in the loaded dataset.", |args, _kwargs| async move {
        Ok(json!(args.len()))
    })
    .build_one_shot()?;

पूर्ण trait रूप के लिए HostFunction (name, description, LLM प्रॉम्प्ट के लिए वैकल्पिक signature, और JSON-में परिवर्तित स्थितीय और कीवर्ड आर्ग्युमेंट के साथ async call) लागू करें। रजिस्ट्री का सत्यापन build_*() पर होता है: नाम मान्य Python identifiers होने चाहिए, अद्वितीय होने चाहिए, और Python built-ins से टकराने नहीं चाहिए।

किसी स्क्रिप्ट के भीतर होस्ट फ़ंक्शन सिंक्रोनस रूप से कॉल किए जाते हैं — कभी भी await के साथ नहीं। लौटाया गया Err संदेश वाला ऐसा Python exception बन जाता है जिसे पकड़ा जा सकता है; किसी अपंजीकृत नाम को कॉल करने पर एक सुधारात्मक exception उठता है, जिसमें पंजीकृत नामों की सूची होती है। होस्ट-फ़ंक्शन निष्पादन की अपनी wall-clock सीमा होती है (host_function_timeout, डिफ़ॉल्ट 30 s), इसलिए अटका हुआ फ़ंक्शन execute() को अवरुद्ध नहीं कर सकता।

नोट: होस्ट फ़ंक्शन होस्ट कोड के रूप में चलते हैं। वे उपयोगकर्ता की अपनी trust boundary हैं, Monty की नहीं — interpreter sandbox उनके side effects को सीमित नहीं करता।

स्वयं-वर्णन करने वाले executor

दोनों executor CodeExecutor::prompt_snippet() को लागू करते हैं और अपनी निर्मित क्षमताओं को रेंडर करते हैं: मोड semantics, access levels के साथ filesystem roots, environment variable names (values कभी रेंडर नहीं किए जाते), clock availability, no-network/no-subprocess गारंटी, output contract, और registered host functions के लिए Python stub block। MontyPythonCodeTool इस snippet को अपने LLM-उन्मुख विवरण में जोड़ता है, इसलिए prompt और interpreter के भीतर का व्यवहार एक ही configuration से प्राप्त होते हैं और उनमें drift नहीं हो सकता।

MontyPythonCodeTool

Agent-facing tool (monty_python_code, scope code:execute) JavaScriptCodeTool का प्रतिबिंब है: error-as-information JSON, camelCase output keys, और feature अक्षम होने पर structured "rejected" fallback।

use adk_code::PathAccess;
use adk_tool::MontyPythonCodeTool;
use serde_json::json;
use std::sync::Arc;

let tool = MontyPythonCodeTool::builder()
    .allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
    .environ_var("PROJECT", "acme")
    .system_clock()
    .function_fn("get_weather", "Current weather for a city.", |args, _kwargs| async move {
        Ok(json!({ "temp_c": 21 }))
    })
    .build_repl()?;

let agent = LlmAgentBuilder::new("data_agent")
    .instruction("Use monty_python_code for calculations and data work.")
    .model(model)
    .tool(Arc::new(tool))
    .build()?;

MontyPythonCodeTool::new() पूरी तरह sandboxed one-shot tool बनाता है; MontyPythonCodeTool::repl() पूरी तरह sandboxed REPL tool बनाता है।

Session scoping

REPL mode में, interpreter sessions को पूर्ण ADK session identity — app name, user id, और session id — के आधार पर keyed किया जाता है, इसलिए users के बीच state कभी leak नहीं होती, भले ही session id strings अलग-अलग users में दोहराई जाएँ। सभी sessions समान grants और host-function registry साझा करते हैं — केवल interpreter state per-session होती है। Session map LRU cap (max_sessions, default 100; 0 को 1 माना जाता है) द्वारा सीमित है; evicted session की अगली call पारदर्शी रूप से एक fresh interpreter शुरू करती है।

Tool arguments

तर्कप्रकारविवरण
codestring (आवश्यक)निष्पादित करने के लिए Python स्रोत
inputanyJSON का वैकल्पिक मान, जो input चर से संबद्ध है
timeout_secsintegerइंटरप्रेटर का समय बजट (डिफ़ॉल्ट 30, 1–300 तक सीमित)
resetbooleanकेवल REPL मोड: निष्पादन से पहले स्थायी सत्र को त्याग दें

आउटपुट एनवेलप

{ "status": "success", "stdout": "", "stderr": "", "output": {"n": 42},
  "stdoutTruncated": false, "stderrTruncated": false, "durationMs": 3 }

कोई exitCode नहीं है — निष्पादन प्रक्रिया के भीतर होता है, कोई प्रक्रिया शुरू नहीं की जाती; status सफलता/विफलता का संकेत है। stdoutTruncated / stderrTruncated तब रिपोर्ट करते हैं जब कैप्चर किया गया आउटपुट सैंडबॉक्स नीति की बाइट सीमा (प्रत्येक के लिए डिफ़ॉल्ट रूप से 1 MB) पर कट जाता है।

स्क्रिप्ट की अंतिम अभिव्यक्ति का मान output के रूप में लौटाया जाता है; print() आउटपुट को stdout के रूप में कैप्चर किया जाता है। विफलता स्थितियाँ: "failed" (Python अपवाद — stderr में ट्रेसबैक, जिसमें होस्ट फ़ंक्शन द्वारा उठाए गए अपवाद भी शामिल हैं), "timeout" (समय बजट पार हो गया), "rejected" (अमान्य आर्ग्युमेंट या सुविधा अक्षम)। कभी भी ToolError नहीं।

दोनों मोड में एनवेलप निश्चित रहता है, इसलिए उपकरण इसे Tool::response_schema() के माध्यम से घोषित करता है — वे प्रदाता जो प्रतिक्रिया स्कीमा उपलब्ध कराते हैं, उन्हें यह उपकरण घोषणा में parameters के साथ प्राप्त होता है।

CodeAct से संबंध

CodeActAgent + adk-codeact-monty पथ भी Monty के माध्यम से Python चलाता है, लेकिन स्क्रिप्ट के भीतर से ADK Tool डिस्पैच (call_tool(...)) और एजेंट टर्न के बीच निलंबन/पुनःआरंभ के साथ। MontyPythonCodeTool जानबूझकर दोनों को बाहर रखता है — यह एक स्व-निहित कोड-निष्पादन उपकरण है, जिसकी विस्तारशीलता की सीमा होस्ट-फ़ंक्शन रजिस्ट्री है। Coding Agent में CodeAct देखें।

उदाहरण

examples/monty_python_code_tool एक LlmAgent चलाता है, जिसमें एक REPL-मोड MontyPythonCodeTool को रीड-राइट माउंट, एक पर्यावरण चर और एक पंजीकृत होस्ट फ़ंक्शन के साथ कॉन्फ़िगर किया गया है — यह मॉडल द्वारा लिखे गए Python से बहु-टर्न चर स्थायित्व और होस्ट-फ़ंक्शन कॉल प्रदर्शित करता है।