Shell araçları

Beta agent-framework-tools Python paketi, agent_framework.tools ad alanı üzerinden kabukta yürütme ve ortam farkındalığı araçları sağlar.

Araç Şu durumlarda kullanın:
LocalShellTool Komutlar güvenilir veya tek tek onaylanmıştır ve aracı işleminin ana bilgisayar ortamında çalıştırılmalıdır.
DockerShellTool Model tarafından oluşturulan kabuk komutları, OCI kapsayıcı yalıtımı gerektirir.
ShellEnvironmentProvider Modelin etkin kabuk ailesine, işletim sistemine, çalışma dizinine ve yüklü CLI sürümlerine ihtiyacı vardır.
ShellPolicy Onay veya yürütmeden önce bir izin listesi veya reddetme listesi ön filtresi istiyorsunuz.

Warning

Kabuk yürütme dosyaları değiştirebilir, işlemleri başlatabilir, kimlik bilgilerine erişebilir ve dış sistemlerle iletişim kurabilir. Görevi destekleyen en az ayrıcalıklı yürütme katmanını kullanın.

Paketi yükle

dotnet add package Microsoft.Agents.AI.Tools.Shell --prerelease

Yerel kabuk ve ortam farkındalığını kullanma

LocalShellExecutor durum bilgisi olmayan ve kalıcı modları destekler. ShellEnvironmentProvider etkin ortamı yoklar ve aracı bağlamı için yetkili kabuk yönergeleri ekler.

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Tools.Shell;
using Microsoft.Extensions.AI;

var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";

// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());

const string Instructions = """
    You are an agent with a single tool: run_shell. Use it to satisfy the
    user's request. Do not describe what you would do — actually run the
    commands. Reply with the final answer derived from real output.
    """;

// --------------------------------------------------------------------
// 1. Stateless mode — each call gets a fresh shell.
// --------------------------------------------------------------------
Console.WriteLine("### Stateless mode\n");
await using (var statelessShell = new LocalShellExecutor(new() { Mode = ShellMode.Stateless, AcknowledgeUnsafe = true }))
{
    var envProvider = new ShellEnvironmentProvider(statelessShell);
    var statelessAgent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions
    {
        ChatOptions = new()
        {
            ModelId = deploymentName,
            Instructions = Instructions,
            Tools = [statelessShell.AsAIFunction(requireApproval: false)],
        },
        AIContextProviders = [envProvider],
    });
// --------------------------------------------------------------------
// 2. Persistent mode — one shell, reused across calls. State carries.
// --------------------------------------------------------------------
Console.WriteLine("\n### Persistent mode\n");
await using (var persistentShell = new LocalShellExecutor(new() { Mode = ShellMode.Persistent, AcknowledgeUnsafe = true }))
{
    var envProvider = new ShellEnvironmentProvider(persistentShell);
    var persistentAgent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions
    {
        ChatOptions = new()
        {
            ModelId = deploymentName,
            Instructions = Instructions,
            Tools = [persistentShell.AsAIFunction(requireApproval: false)],
        },
        AIContextProviders = [envProvider],
    });

    var persistentSession = await persistentAgent.CreateSessionAsync();

    // State carries across calls in persistent mode: cd into temp, then
    // verify the next call sees the new CWD.
    Console.WriteLine(await persistentAgent.RunAsync("Change directory into the system temp folder, then print the current working directory.", persistentSession));
    Console.WriteLine();
    Console.WriteLine(await persistentAgent.RunAsync("In a NEW shell call, print the current working directory again. Tell me whether it still matches the temp folder.", persistentSession));
    Console.WriteLine();

    // Same idea with an exported variable: set in one call, read in the next.
    Console.WriteLine(await persistentAgent.RunAsync("Set the environment variable DEMO_TOKEN to the value 'hello-world'.", persistentSession));
    Console.WriteLine();
    Console.WriteLine(await persistentAgent.RunAsync("Print the current value of DEMO_TOKEN. Tell me exactly what value the shell reports.", persistentSession));
    Console.WriteLine();

    PrintSnapshot(envProvider.CurrentSnapshot!);
}

ShellPolicy , komut ön filtrelemesi için de kullanılabilir. DockerShellExecutor özel çalıştırılabilir örnek şu anda yayımlanmış değil.

Paketi yükle

pip install agent-framework-tools --pre

Paket, bir çalıştırma işlemi zaman aşımına uğradığında alt işlem ağaçlarını sonlandırmak için psutil yükler.

LocalShellTool komutunu kullanma

LocalShellTool komutları doğrudan ana sistem üzerinde çalıştırır. Varsayılan ayarlar, kalıcı bir kabuk, 30 saniyelik bir zaman aşımı, çıktının 64 KiB ile sınırlandırılması ve çalışma dizininin yeniden sabitlenmesi şeklindedir. as_function() üzerinden yapılan ajan çağrıları varsayılan olarak onay gerektirir. run() için yapılan doğrudan çağrılar onay istemez.

import asyncio
from typing import Any

from agent_framework import Agent, Message
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import LocalShellTool
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()
async def main() -> None:
    print("=== OpenAI Agent with LocalShellTool Example ===")
    print("NOTE: Commands will execute on your local machine.\n")

    client = OpenAIChatClient(model="gpt-5.4-nano")

    async with LocalShellTool() as shell:
        agent = Agent(
            client=client,
            instructions="You are a helpful assistant that can run shell commands to help the user.",
            tools=[client.get_shell_tool(func=shell.as_function())],
        )

        query = "Use the shell tool to execute `python --version` and show only the command output."
        print(f"User: {query}")
        result = await run_with_approvals(query, agent)
        if isinstance(result, str):
            print(f"Agent: {result}\n")
            return
        if result.text:
            print(f"Agent: {result.text}\n")
        else:
            printed = False
            for message in result.messages:
                for content in message.contents:
                    if content.type == "function_result" and content.result:
                        print(f"Agent (tool output): {content.result}\n")
                        printed = True
            if not printed:
                print("Agent: (no text output returned)\n")


async def run_with_approvals(query: str, agent: Agent) -> Any:
    """Run the agent and handle shell approvals outside tool execution."""
    current_input: str | list[Any] = query

    while True:
        result = await agent.run(current_input)
        if not result.user_input_requests:
            return result

        next_input: list[Any] = [query]
        rejected = False
        for user_input_needed in result.user_input_requests:
            if user_input_needed.function_call is None:
                continue
            print(
                f"\nShell request: {user_input_needed.function_call.name}"
                f"\nArguments: {user_input_needed.function_call.arguments}"
            )
            user_approval = await asyncio.to_thread(input, "\nApprove shell command? (y/n): ")
            approved = user_approval.strip().lower() == "y"
            next_input.append(Message("assistant", [user_input_needed]))
            next_input.append(Message("user", [user_input_needed.to_function_approval_response(approved)]))
            if not approved:
                rejected = True
                break
        if rejected:
            print("\nShell command rejected. Stopping without additional approval prompts.")
            return "Shell command execution was rejected by user."
        current_input = next_input


if __name__ == "__main__":
    asyncio.run(main())

stdout doğrudan çağrıları, ayrı exit_code, stderr, truncated, timed_out, ShellResult ve run() alanlarına sahip bir duration_ms döndürür. shell OpenAI Yanıtları veya Foundry barındırma aracılığıyla çalıştığında, Agent Framework sağlayıcılar arası devamlılıkta standart çıkışı, standart hatayı, çıkış sonuçlarını ve zaman aşımı sonuçlarını korur. Sıfır dışı çıkışlar hata olarak kalır, standart hata ayrı kalır ve zaman aşımı başarılı bir çıkış olarak gösterilmez.

OpenAI sağlayıcısı tarafından barındırılan kabuk transkript öğeleri, yerel bir kabuk yürütücüsü yapılandırıldığında bile bilgilendirici kalır. Yalnızca iyi biçimlendirilmiş açık bir local_shell_call ya da environment.type="local" ile işaretlenmiş bir kabuk çağrısı, yerel işlev ve onay yoluna girer. LocalShellTool öğesini tek başına yapılandırmak, sağlayıcı tarafından barındırılan kabuk çağrılarının konakta yürütülmesine neden olmaz.

Her çağrının yeni bir işlemde çalıştırılması gerektiğinde kullanın mode="stateless" . AGENT_FRAMEWORK_SHELL Çözümlenen kabuğu geçersiz kılmak için ortam değişkenini veya shell oluşturucu bağımsız değişkenini kullanın.

Kalıcı komutlar serileştirilir. Çıkış taşmasından sonra veya oturumu kurtarılamaz durumda bırakan bir zaman aşımından sonra, kuyruğa alınan komut yeni bir kabukta başlar ve sonlandırılan oturumdaki kabuk yerel değişkenlerini, işlevlerini ve çalışma dizini değişikliklerini kaybeder. Uygun şekilde kesintiye uğratılan POSIX zaman aşımı, mevcut kabuğu ve durumunu koruyabilir. Windows'ta zaman aşımı, kesintiye uğrayan komut tamamlanma işaretçisini yaysa bile her zaman mevcut kalıcı kabuğu kapatır.

close() çağrısı, kabuğu yeniden başlatmak yerine kapatmadan önce veya kapatma sırasında kuyruğa alınan komutları reddeder. Daha sonraki bir çağrı yeni bir oturum başlatabilir.

Önemli

LocalShellTool bir sandbox değildir. İnsan onayı bir gözden geçirme kapısı ekler, ancak kabuğu yalıtmaz. Aracı çağrıları için onayın devre dışı bırakılması acknowledge_unsafe=True gerektirir.

ile komutları kısıtla ShellPolicy

ShellPolicy yürütmeden önce komut metnine normal ifade izin verme ve reddetme listeleri uygular. Reddetme kuralları önceliklidir. Kabuğun sonuçta ne yürüttüğü veya dosya erişimini kısıtladığı incelenemez.

import asyncio

from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import LocalShellTool, ShellPolicy
from dotenv import load_dotenv
load_dotenv()
async def main() -> None:
    client = OpenAIChatClient(model="gpt-5.4-nano")

    shell = LocalShellTool(
        mode="stateless",
        # Unsafe for production as shown: these filters do not replace human approval or isolation.
        approval_mode="never_require",
        acknowledge_unsafe=True,
        policy=ShellPolicy(
            allowlist=[
                r"^ls(\s|$)",
                r"^pwd$",
                r"^cat\s[^|;&]+$",
                r"^git\s+(status|log|diff)(\s|$)",
                r"^python\s+--version$",
            ],
        ),
        timeout=10,
    )

    agent = Agent(
        client=client,
        instructions=("Use only these shell commands: ls, pwd, cat, git status/log/diff, python --version."),
        tools=[client.get_shell_tool(func=shell.as_function())],
    )

Warning

Örnek, insan onayını devre dışı bırakır ve yalnızca gizli bilgiler veya değerli veriler içermeyen yalıtılmış, tek kullanımlık bir ortam için uygundur. ve backticks gibi $(...) komut değiştirmeleri basit izin verme listesi desenlerini geçirebilir. Yalnızca komutun başlangıcıyla eşleşen desenler ek işlemlere de izin verebilir. Üretim için ayrı ayrı uygulanan yalıtım ve kısıtlı izinler kullanın. İnsan incelemesi ek bir kontrol sağlayabilir, ancak kabuğu izole etmez.

Dize desenlerini tercih edin. Python, dizeleri regex altyapısını kullanarak derler ve her eşleşme için bir saniyelik süre sınırı uygular. Bir engelleme listesi zaman aşımı, komutun reddedilmesine neden olur; izin verilenler listesi zaman aşımı ise izin sağlamaz. Önceden derlenmiş regex.Pattern, aynı sınırı kullanır. Önceden derlenmiş bir standart kitaplık re.Pattern bayraklarını korur ancak kesintiye uğratılamaz, bu nedenle bu formdaki pahalı veya belirsiz ifadelerden kaçının.

ShellEnvironmentProvider ekle

ShellEnvironmentProvider kabuk ailesini, sürümünü, işletim sistemini, çalışma dizinini ve seçili CLI sürümlerini yoklar, ardından aracı çalışmadan önce bu bilgileri ekler. Varsayılan yoklama listesi , git, nodeve pythonşeklindedirdocker.

import asyncio

from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import (
    LocalShellTool,
    ShellEnvironmentProvider,
    ShellEnvironmentProviderOptions,
)
from dotenv import load_dotenv
load_dotenv()
def _print_snapshot(label: str, provider: ShellEnvironmentProvider) -> None:
    snapshot = provider.current_snapshot
    if snapshot is None:
        print(f"[{label}] no snapshot captured")
        return
    print(f"\n[{label}] snapshot:")
    print(f"  family            = {snapshot.family.value}")
    print(f"  os                = {snapshot.os_description}")
    print(f"  shell_version     = {snapshot.shell_version}")
    print(f"  working_directory = {snapshot.working_directory}")
    for tool, version in snapshot.tool_versions.items():
        print(f"  {tool:<17} = {version}")


async def _ask(agent: Agent, query: str) -> None:
    print(f"\nUser: {query}")
    result = await agent.run(query)
    if result.text:
        print(f"Agent: {result.text}")


async def main() -> None:
    client = OpenAIChatClient(model="gpt-5.4-nano")
    options = ShellEnvironmentProviderOptions(
        probe_tools=("git", "python", "uv", "node"),
    )

    print("=== stateless mode ===")
    async with LocalShellTool(
        mode="stateless",
        approval_mode="never_require",
        acknowledge_unsafe=True,
    ) as shell:
        provider = ShellEnvironmentProvider(shell, options)
        agent = Agent(
            client=client,
            instructions="Use the shell tool to answer the user's question.",
            tools=[client.get_shell_tool(func=shell.as_function())],
            context_providers=[provider],
        )
        await _ask(agent, "Show me the current working directory.")
        await _ask(agent, "Now `cd ..` then show the working directory again.")
        await _ask(agent, "Show the working directory once more — did `cd` persist?")
        _print_snapshot("stateless", provider)

    print("\n=== persistent mode ===")
    async with LocalShellTool(
        mode="persistent",
        confine_workdir=False,
        approval_mode="never_require",
        acknowledge_unsafe=True,
    ) as shell:
        provider = ShellEnvironmentProvider(shell, options)
        agent = Agent(
            client=client,
            instructions="Use the shell tool to answer the user's question.",
            tools=[client.get_shell_tool(func=shell.as_function())],
            context_providers=[provider],
        )
        await _ask(agent, "Show me the current working directory.")
        await _ask(agent, "Now `cd ..` then show the working directory again.")
        await _ask(agent, "Show the working directory once more — did `cd` persist?")
        _print_snapshot("persistent", provider)

DockerShellTool komutunu kullanma

DockerShellTool, PATH üzerinde Docker veya Podman gerektirir. Varsayılanlar ağı devre dışı bırakır, kök olmayan bir kullanıcı olarak çalışır, salt okunur bir kök dosya sistemi kullanır, özellikleri bırakır, belleği 512 MiB ile sınırlar ve kapsayıcıyı 256 işlemde kaplar.

from agent_framework.tools import DockerShellTool

async with DockerShellTool(
    image="mcr.microsoft.com/azurelinux/base/core:3.0",
    approval_mode="never_require",
) as shell:
    result = await shell.run("uname -a && id")
    print(result.stdout)

Varsayılan görüntü: mcr.microsoft.com/azurelinux/base/core:3.0. Podman kullanmak için docker_binary="podman" iletin. DockerShellTool özel çalıştırılabilir örnek şu anda yayımlanmış değil.

Yalnızca yapılandırılan yalıtımı veya kaynak sınırlarını zayıflatmayan Docker seçenekleri için kullanın extra_run_args . Doğrulama uzun bayrakları, kısa bayrakları, ekli değerleri ve kümelenmiş kısa bayrakları tanır. --memory -m / , --network--volume-v, / --pids-limit-u, / ve --user gibi geçersiz kılmaları reddeder. Bunun yerine ilgili DockerShellTool oluşturucu seçeneğini kullanın.

Yürütme katmanı seçme

Scenario Araç Yalıtım sınırı
Güvenilen geliştirme komutları LocalShellTool Yok; insan onayı varsayılan olarak etkindir
Güvenilir olmayan kabuk komutları DockerShellTool Varsayılan yalıtım bayraklarına sahip OCI kapsayıcısı
Shell olmadan güvenilmeyen üretilen kod Hyperlight CodeAct Hyperlight microVM

Go, tool/shelltool aracılığıyla yerel kabukta yürütme ve ortamı yoklama olanağı sağlar. Bkz. Yerel kabuk aracını kullanın.

DockerShellTool rehberliği şu anda Go için mevcut değil.

Harness Agent ile kabuk araçlarını kullanmak

Yalın aracılar ve HarnessAgent, aynı iki parçalı kabuk yapılandırmasını kullanır: yürütücünün işlevini bir araç olarak kaydedin ve modelin kabuk, işletim sistemi, çalışma dizini ve CLI sürümü bağlamını alması gerektiğinde ShellEnvironmentProvider ekleyin. HarnessAgent bir shell yürütücüsü oluşturmaz veya bir shell yürütücüsüne sahip değildir:

using System.IO;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Tools.Shell;
using Microsoft.Extensions.AI;

await using var shell = new LocalShellExecutor(new LocalShellExecutorOptions
{
    WorkingDirectory = Directory.GetCurrentDirectory(),
    Timeout = LocalShellExecutor.DefaultTimeout,
});

AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
    AIContextProviders = [new ShellEnvironmentProvider(shell)],
    ChatOptions = new ChatOptions
    {
        Tools = [shell.AsAIFunction(requireApproval: true)],
    },
});

AsAIFunction, varsayılan olarak run_shell ve requireApproval: true adını kullanır. LocalShellExecutor varsayılan olarak kalıcı mod, çıkış akışı başına 64 KiB üst sınır ve zaman aşımı yoktur; örnek, önerilen 30 saniyeyi LocalShellExecutor.DefaultTimeoutaçıkça kullanır. ShellEnvironmentProviderOptions, varsayılan olarak git, dotnet, node, python ve docker öğelerini her yoklama için beş saniyelik zaman aşımıyla yoklar.

Kullanıcı oturumu başına bir kalıcı yürütücü oluşturun ve oturum sona erdiğinde bunu atın. Çalışma dizini, ortam, kabuk geçmişi, arka plan işleri ve komut kuyruğu paylaşıldığından, bunu kullanıcılar veya eşzamanlı konuşmalar arasında paylaşmayın. ShellPolicy yalnızca ön filtredir; onayı etkin tutun, en az ayrıcalıklı kimlik bilgilerini kullanın ve komutların daha güçlü bir yalıtım sınırı gerektirdiğini tercih edin DockerShellExecutor .

Komut kabuğu araçları, ön sürüm Microsoft.Agents.AI.Tools.Shell paketinde sunulmaktadır. HarnessAgent, Microsoft.Agents.AI.Harness aracılığıyla kullanılabilir.

Normal bir aracı için, kabuk işlevini client.get_shell_tool(func=shell.as_function()) ile oluşturun ve ShellEnvironmentProvider’i ayrı olarak ekleyin. create_harness_agent geçirdiğinizde shell_executorher iki adımı da gerçekleştirir:

from agent_framework import create_harness_agent
from agent_framework.tools import LocalShellTool, ShellEnvironmentProviderOptions

async with LocalShellTool() as shell:
    agent = create_harness_agent(
        client=client,
        shell_executor=shell,
        shell_environment_provider_options=ShellEnvironmentProviderOptions(
            probe_tools=("git", "python"),
        ),
    )

    session = agent.create_session()
    response = await agent.run("Inspect the current repository.", session=session)

shell_executor isteğe bağlıdır ve as_function() sunmalıdır. Fabrika, kabuk aracını ve ShellEnvironmentProvider öğesini yalnızca istemci SupportsShellTool öğesini uyguladığında ekler; aksi takdirde bir uyarı günlüğe kaydeder ve her ikisini de atlar. shell_environment_provider_options isteğe bağlıdır ve yalnızca ile shell_executorkullanılır.

LocalShellTool varsayılan olarak kalıcı modu, 30 saniyelik zaman aşımını, 64 KiB birleşik çıktıyı, çalışma dizininin yeniden sabitlenmesini ve approval_mode="always_require" kullanır. Harness aracı onayı varsayılan olarak etkin olduğundan, AgentSession öğesini run öğesine iletin. Yürütücünün yaşam döngüsü çağıranın sorumluluğundadır; async with kullanın veya close() çağrısı yapın ve her kullanıcı oturumu için tek bir kalıcı araç oluşturun. Değiştirilebilir kabuk durumunu kullanıcılar veya eşzamanlı konuşmalar arasında paylaşmayın.

Ana bilgisayar kabuğu, korumalı alan değildir. Onayı etkin bırakın, en düşük ayrıcalığa sahip kimlik bilgilerini kullanın ve kapsayıcı yalıtımı için DockerShellTool kullanın. Onayı devre dışı bırakmak için approval_mode="never_require" ve acknowledge_unsafe=True gerekir; ShellPolicy tek başına bir güvenlik sınırı değildir.

create_harness_agent, agent-framework-core sürümünde yayınlanır. Kabuk entegrasyonu, ön sürüm agent-framework-tools paketi tarafından sağlanır ve etkinleştirildiğinde bir ExperimentalWarning üretir.

Paketlenmiş bir Go Harness şu anda mevcut değil. Yerel kabuk aracını ve ortam sağlayıcısını doğrudan yalın bir Go ajanı üzerinde birleştirin.