CodeActAgent (CodeAct)

CodeActAgent, LlmAgent का समकक्ष है, जो एक-एक करके टूल कॉल जारी करने के बजाय कोड लिखकर और चलाकर कार्य करता है। प्रत्येक चरण में मॉडल एकल स्क्रिप्ट तैयार करता है; टूल ऐसी कॉल करने योग्य फ़ंक्शन के रूप में उपलब्ध होते हैं जिन्हें स्क्रिप्ट संयोजित कर सकती है; और स्क्रिप्ट टैग किए गए मान को लौटाकर अपना परिणाम संप्रेषित करती है।

यह CodeAct पैटर्न है: call tool A → observe → call tool B करने के बजाय, मॉडल एक ही स्क्रिप्ट में b(a(x)) लिखता है, इसलिए बहु-चरणीय कार्य एक ही चरण में पूरा हो जाता है। इसे adk-agent पर codeact सुविधा द्वारा सक्षम किया जाता है।

इसका उपयोग कब करें

  • ऐसे कार्य जो प्रत्येक चरण में कई टूल को क्रमबद्ध या संयोजित करते हैं (डेटा व्यवस्थित करना, बैच संचालन, ग्लू लॉजिक)।
  • कोड जनरेशन के लिए पोस्ट-ट्रेन किए गए मॉडल।
  • ऐसे वर्कफ़्लो जिनमें कार्रवाई के आधार के रूप में वास्तविक इंटरप्रेटर (जैसे Python) उपलब्ध हो।

नेटिव टूल-कॉलिंग के लिए LlmAgent को प्राथमिकता दें। सैंडबॉक्स किए गए फ़ाइल/शेल कोडिंग हार्नेस के लिए Coding Agent देखें।

लूप कैसे काम करता है

प्रत्येक चरण में:

  1. मॉडल एक फ़ेंस किया हुआ कोड ब्लॉक (एक स्क्रिप्ट) जारी करता है।
  2. स्क्रिप्ट [CodeRuntime] पर चलती है; टूल कॉल होस्ट तक पहुँचती हैं, जो टूल को निष्पादित करता है और परिणाम के साथ स्क्रिप्ट को फिर से शुरू करता है।
  3. स्क्रिप्ट एक टैग किया हुआ ScriptOutput लौटाती है:
    • observation — मॉडल को वापस भेजा जाता है; लूप जारी रहता है।
    • error — संदेश के रूप में वापस भेजा जाता है; लूप जारी रहता है।
    • final_result — कॉलर को लौटाया जाता है; लूप समाप्त हो जाता है।
    • transfer_to_agent — नियंत्रण किसी अन्य एजेंट को सौंपता है; लूप समाप्त हो जाता है।

फ़्रेमवर्क भाषा-निरपेक्ष है: CodeRuntime trait चरण-दर-चरण इंटरप्रेटर का इंटरफ़ेस है, और यह एक मुक्त-रूप प्रॉम्प्ट के माध्यम से मॉडल को अपनी भाषा/पर्यावरण की जानकारी देता है। इच्छित प्रोडक्शन अडैप्टर Monty को रैप करता है, जो Rust-नेटिव Python इंटरप्रेटर है।

टिकाऊपन: निलंबित करना और फिर से शुरू करना

CodeActAgent इनवोकेशन के बीच स्टेटलेस है — स्थायी स्थिति LlmAgent की तरह ही सेशन में रहती है। दो स्थितियाँ रन को स्थगित करती हैं:

  • बिना किसी निर्णय वाले पुष्टि-आधारित टूल (HITL), और
  • ऐसा लंबे समय तक चलने वाला टूल जिसका परिणाम आउट-ऑफ-बैंड प्राप्त होता है।

स्थगन के समय, लाइव इंटरप्रेटर कंटिन्यूएशन को CodeActCheckpoint में सीरियलाइज़ करके सेशन स्थिति में लिखा जाता है; अगला run() उसे वापस पढ़कर रन को जारी रखता है — पुष्टि का निर्णय RunConfig::tool_confirmation_decisions के माध्यम से आता है, और लंबे समय तक चलने वाले टूल का परिणाम अगले संदेश में FunctionResponse के रूप में आता है। इनलाइन टूल कॉल को write-ahead (SAVE-BEFORE) और SAVE-AFTER चेकपॉइंट से घेरा जाता है: एक बार SAVE-AFTER चेकपॉइंट स्थायी रूप से सहेज दिए जाने पर, रिकवरी संग्रहीत परिणाम के साथ जारी रहती है और टूल को दोबारा कभी नहीं चलाती। टूल के साइड इफ़ेक्ट के बाद, लेकिन उसके SAVE-AFTER चेकपॉइंट के दर्ज होने से पहले की संकीर्ण अवधि में क्रैश होने पर, रिकवरी के दौरान टूल फिर से चलाया जाएगा; इसलिए जो टूल idempotent नहीं हैं, उन्हें इससे बचाव करना चाहिए (LlmAgent की तरह ही कम-से-कम-एक-बार निष्पादन की सीमा)।

इसके लिए ऐसे रनटाइम की आवश्यकता होती है जो रुकी हुई कॉल का स्नैपशॉट ले सके। जो रनटाइम ऐसा नहीं कर सकता, वह लंबे समय तक चलने वाले टूल को इनलाइन चलाता है और पुष्टि-आधारित स्थगन को अस्वीकार करता है।

CodeActAgent बनाना

use adk_agent::codeact::CodeActAgent;
use std::sync::Arc;

// `model` implements `adk_core::Llm`; `runtime` implements `CodeRuntime`.
let agent = CodeActAgent::builder()
    .name("analyst")
    .model(model)
    .runtime(runtime)
    .instruction("Prefer concise, composable steps.")
    .tool(Arc::new(load_csv_tool))
    .output_key("report")
    .build()?;

model और runtime आवश्यक हैं; बाकी सभी के लिए डिफ़ॉल्ट मान है।

LlmAgent के साथ समानता

बिल्डर LlmAgentBuilder का प्रतिबिंब है:

  • मॉडल: generate_content_config साथ ही temperature/top_p/top_k/ max_output_tokens संक्षिप्त रूप।
  • निर्देश: instruction/instruction_provider, global_instruction/global_instruction_provider, जिसमें {state.key} टेम्पलेट इंजेक्शन शामिल है; साथ ही कौशल (skills सुविधा)।
  • इतिहास: include_contents
  • टूल: स्थिर tools और प्रत्येक आह्वान के toolsets; tool_timeout, default_retry_budget/tool_retry_budget, circuit_breaker_threshold, और on_tool_error फ़ॉलबैक।
  • प्राधिकरण: ToolConfirmationPolicy (require_tool_confirmation/require_tool_confirmation_for_all)।
  • स्थानांतरण: sub_agents और disallow_transfer_to_parent/ disallow_transfer_to_peers
  • आउटपुट: output_key, output_schema/output_type के साथ सुधार-पुनःप्रयास लूप (output_max_retries)।
  • कॉलबैक: before_callback/after_callback, before_model_callback/after_model_callback, और before_tool_callback/after_tool_callback/after_tool_callback_full। टूल के बाद वाले कॉलबैक संरचित निष्पादन मेटाडेटा का निरीक्षण CallbackContext::tool_outcome() के माध्यम से कर सकते हैं।
  • सुविधा-आधारित: इनपुट/आउटपुट सुरक्षा-नियंत्रण (guardrails) और EnhancedPlugin पाइपलाइन (enhanced-plugins)।

प्रत्येक टूल कॉल को एक नया ToolContext मिलता है, जो इंटरप्रेटर कॉल आईडी रखता है और आर्टिफैक्ट, मेमोरी, साझा स्थिति, उपयोगकर्ता स्कोप तथा सीक्रेट्स को सक्रिय आह्वान को सौंपता है — इसलिए कोई टूल CodeActAgent या LlmAgent के अंतर्गत समान रूप से व्यवहार करता है।

जानबूझकर किए गए अंतर

  • कोड-निष्पादन सैंडबॉक्सिंग CodeRuntime की ज़िम्मेदारी है, कोई अतिरिक्त सुविधा नहीं।
  • टूल डिस्पैच डिज़ाइन के अनुसार क्रमिक है (एकल कंटिन्यूएशन को एक कॉल सीमा पर स्नैपशॉट किया जाता है), इसलिए कोई समानांतर tool_execution_strategy नहीं है।
  • skip_summarization बिल्डर विकल्प मौजूद नहीं है — मॉडल स्वयं final_result के माध्यम से लूप समाप्त करता है — हालांकि skip_summarization को अपनी कार्रवाइयों पर सेट करने वाला टूल रन को फिर भी समाप्त कर देता है।

उदाहरण

एक चलने योग्य, निर्भरता-रहित एंड-टू-एंड डेमो — एक स्व-निहित CodeRuntime और एक नियतात्मक मॉडल — यहाँ स्थित है: examples/codeact_agent:

cargo run --manifest-path examples/codeact_agent/Cargo.toml

CodeRuntime लागू करना

एक CodeRuntime स्क्रिप्ट को पार्स करके चरणों में चलाता है और एक समय में एक बाहरी कॉल उपलब्ध कराता है:

pub trait CodeRuntime: Send + Sync {
    fn start(&self, script: &str, script_name: &str) -> Result<RunStep, RuntimeError>;
    fn resume(&self, snapshot: &[u8], with: ResumeWith) -> Result<RunStep, RuntimeError>;
    fn capabilities(&self) -> RuntimeCapabilities { /* default */ }
    fn render_tools(&self, tools: &[Arc<dyn Tool>]) -> String { /* default */ }
}
  • RunStep struct के वेरिएंट का एक सेट है — Call { call, stdout }, Complete { value, stdout } और Raised { message, stdout }। इन्हें RunStep::call / RunStep::complete / RunStep::raised हेल्पर के साथ बनाएँ और कैप्चर किए गए आउटपुट को .with_stdout(..) के साथ जोड़ें। RunStep::Call ठीक एक लंबित कॉल उपलब्ध कराता है; इसे किसी मान या त्रुटि के साथ फिर से शुरू करें, या निलंबित करने के लिए इसके कंटिन्यूएशन को dump() करें। रनटाइम द्वारा जोड़ा गया stdout मॉडल को वापस उपलब्ध कराया जाता है और चेकपॉइंट में स्थायी रूप से सहेजा जाता है, इसलिए वह निलंबन/पुनःआरंभ के दौरान भी बना रहता है।
  • एक PendingCall अपने आर्ग्युमेंट को उसी तरह रिपोर्ट करता है जैसे इंटरप्रेटर ने उन्हें उत्पन्न किया था — positional_args() और keyword_args() को अलग-अलग। पोज़िशनल आर्ग्युमेंट को स्वयं नामों पर मैप न करें: ड्राइवर उन्हें adk_agent::codeact::bind_call_args के माध्यम से टूल के पैरामीटर से केंद्रीय रूप से बाँधता है, इसलिए कॉल सीमा पर रनटाइम को किसी टूल स्कीमा की आवश्यकता नहीं होती और render_tools टूल स्लाइस का शुद्ध फ़ंक्शन हो सकता है।
  • स्क्रिप्ट बनाम होस्ट त्रुटियाँ। मॉडल अलग कोड लिखकर जिस भी चीज़ को ठीक कर सकता है — सिंटैक्स/पार्स त्रुटि, बिना पकड़ी गई एक्सेप्शन, संसाधन-सीमा के कारण रद्दीकरण — वह एक RunStep::Raised है (एक अपारदर्शी स्ट्रिंग, जिसे मॉडल को हूबहू वापस भेजा जाता है)। RuntimeError केवल वास्तविक होस्ट विफलताओं (स्नैपशॉट का (डी)सीरियलाइज़ेशन, इंटरप्रेटर की आंतरिक त्रुटियाँ) के लिए आरक्षित है और रन को रोक देता है।
  • HITL और लंबे समय तक चलने वाले स्थगन को सक्षम करने के लिए RuntimeCapabilities::supports_suspension का true होना आवश्यक है; prompt मॉडल को भाषा/पर्यावरण का वर्णन करता है।

निलंबन/पुनःआरंभ का समर्थन करने वाले पूर्ण, न्यूनतम कार्यान्वयन के लिए examples/codeact_agent/src/runtime.rs देखें।

Monty के माध्यम से Python

उत्पादन के लिए इच्छित adapter है adk-codeact-monty, जो Pydantic Monty द्वारा समर्थित CodeRuntime है। यह मॉडल को Python लिखकर कार्य करने देता है, बिना किसी container या subprocess के in-process चलता है, और रुके हुए run का snapshot bytes में लेता है — ठीक वही जिसकी suspend/resume को आवश्यकता होती है। Monty interpreter का उपयोग adk-code के embedded-python kernel के माध्यम से किया जाता है, जो monty crates को एक ही स्थान पर pin करता है; rustc 1.95+ आवश्यक है।

[dependencies]
adk-agent = { version = "2.1.0", features = ["codeact"] }
adk-codeact-monty = "2.1.0"

या umbrella crate के माध्यम से (adk_rust::codeact_monty के रूप में re-exported):

[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "codeact-monty"] }
use adk_codeact_monty::MontyRuntime;

// Conservative default resource limits (per-advance time + memory caps) make
// `new()` safe for untrusted, LLM-generated code.
let runtime = Arc::new(MontyRuntime::new());

// Tighten or relax with the builder; `unlimited()` removes the caps for
// trusted scripts only.
let runtime = Arc::new(
    MontyRuntime::builder()
        .max_duration(std::time::Duration::from_secs(2))
        .max_memory(64 * 1024 * 1024)
        .build(),
);

OS access

किसी script द्वारा किए जाने वाले operating-system प्रभाव — filesystem reads/writes, os.getenv/os.environ, और date.today()/datetime.now() — host द्वारा नियंत्रित policy के अनुसार in place पूरे किए जाते हैं। ये tools नहीं हैं और agent loop को कभी pause नहीं करते। डिफ़ॉल्ट रूप से runtime पूरी तरह sandboxed होता है (filesystem access नहीं, environment खाली, host clock सक्षम)। builder के साथ विशिष्ट access प्रदान करें:

use adk_codeact_monty::{MontyRuntime, PathAccess};

let runtime = Arc::new(
    MontyRuntime::builder()
        // Mount host directories at virtual paths; Monty enforces the boundary
        // (canonicalization + symlink-escape detection) so a script can never
        // escape a mount. Reads/writes outside every mount raise PermissionError.
        .allow_path("/data", "/srv/agent/data", PathAccess::ReadOnly)
        .allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
        // Expose an explicit environment map to os.getenv / os.environ. Empty by
        // default — the host process environment is never exposed implicitly.
        .environ_var("PROJECT", "acme")
        // date.today() / datetime.now() read the host clock (enabled by default).
        .system_clock(true)
        .build(),
);

Network और subprocess access के लिए कोई Monty OS-call surface नहीं है और policy के बावजूद ये अनुपलब्ध रहते हैं। प्रदान किए गए access का वर्णन model को system prompt में किया जाता है, ताकि उसे पता हो कि वह किन paths को पढ़ या लिख सकता है और कौन-से environment variables मौजूद हैं।

Monty, pathlib.Path के केवल एक subset को लागू करता है, इसलिए जब paths mount किए जाते हैं, तो prompt में समर्थित methods की सटीक सूची दी जाती है (किसी अन्य method पर AttributeError उत्पन्न होता है):

  • पढ़ना/क्वेरी करना (किसी भी mount पर): exists(), is_file(), is_dir(), is_symlink(), read_text(), read_bytes(), stat(), iterdir(), resolve(), absolute(), open("r")
  • लिखना (केवल read-write mounts पर): write_text(), write_bytes(), append_text(), append_bytes(), mkdir(), unlink(), rmdir(), rename(), open("w")/open("a")
  • शुद्ध path operations (कोई I/O नहीं): / operator और joinpath(), is_absolute(), with_name(), with_stem(), with_suffix(), as_posix(), तथा .name, .parent, .stem, .suffix, .suffixes, .parts properties।

Tools को एकल अंतर्निहित function, call_tool("name", {"arg": value, ...}) के माध्यम से invoke किया जाता है — tool को call करने का यही एकमात्र तरीका है; वे कभी भी bare callables के रूप में scope में नहीं होते। Tool का नाम string literal होता है और प्रत्येक argument एक dict में string-keyed entry होता है, इसलिए वास्तविक नाम serialized continuation के भीतर जाता है (host-side name table के बिना suspend/resume के दौरान भी सुरक्षित रहता है), किसी tool और प्रत्येक argument का कोई भी नाम हो सकता है (जैसे "fetch-cart" जैसा valid Python identifier न हो, Python keyword हो, या यहाँ तक कि "call_tool" भी हो), और driver dict की entries को नाम के आधार पर ठीक उसी तरह bind करता है — positional inference नहीं किया जाता। प्रत्येक tool prompt में उसके parameters और description के साथ एक call_tool("name", {...}) usage line के रूप में दिखाई देता है। इस एकमात्र form के अलावा कुछ भी — bare fetch_cart(...), keyword arguments, non-dict argument, या non-string key — चुपचाप dispatch करने के बजाय corrective error के साथ अस्वीकार कर दिया जाता है, इसलिए model को सीखने के लिए calling form बिल्कुल एक ही मिलता है।

Runnable examples/codeact_monty_agent पूरी तरह offline रहते हुए वास्तविक Python के विरुद्ध एक CodeActAgent चलाता है।