Alat shell

Paket Python beta agent-framework-tools menyediakan alat untuk eksekusi shell dan kemampuan mengenali lingkungan melalui namespace agent_framework.tools.

Alat Gunakan saat
LocalShellTool Perintah tepercaya atau disetujui secara individual dan harus berjalan di lingkungan host proses agen.
DockerShellTool Perintah shell yang dihasilkan model memerlukan isolasi kontainer OCI.
ShellEnvironmentProvider Model ini membutuhkan keluarga shell aktif, sistem operasi, direktori kerja, dan versi CLI yang diinstal.
ShellPolicy Anda memerlukan filter awal berupa daftar yang diizinkan atau daftar yang ditolak sebelum persetujuan atau eksekusi.

Warning

Eksekusi shell dapat memodifikasi file, meluncurkan proses, mengakses kredensial, dan berkomunikasi dengan sistem eksternal. Gunakan tingkat eksekusi dengan hak istimewa paling rendah yang mendukung tugas tersebut.

Pasang paketnya

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

Gunakan shell lokal dan pengenalan lingkungan

LocalShellExecutor mendukung mode stateless dan persisten. ShellEnvironmentProvider menyelidiki lingkungan aktif dan menambahkan panduan shell otoritatif ke konteks agen.

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 juga tersedia untuk prapemfilteran perintah. Contoh runnable khusus DockerShellExecutor saat ini belum dipublikasikan.

Pasang paketnya

pip install agent-framework-tools --pre

Paket ini memasang psutil untuk menghentikan seluruh hierarki proses turunan saat eksekusi melewati batas waktu.

Gunakan LocalShellTool

LocalShellTool menjalankan perintah langsung di host. Secara default, ini menggunakan shell persisten, batas waktu 30 detik, pemotongan output 64 KiB, pembatasan direktori kerja, dan persetujuan untuk setiap perintah.

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())

Gunakan mode="stateless" saat setiap panggilan harus berjalan dalam proses baru. Gunakan variabel lingkungan AGENT_FRAMEWORK_SHELL atau argumen konstruktor shell untuk mengganti shell yang telah ditentukan.

Penting

LocalShellTool bukan kotak pasir. Persetujuan adalah batas keamanan utama. Menonaktifkan persetujuan memerlukan acknowledge_unsafe=True.

Batasi perintah dengan ShellPolicy

ShellPolicy menerapkan daftar izin dan daftar penolakan ekspresi reguler sebelum dijalankan. Aturan penolakan lebih diutamakan.

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",
        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=(
            "You can run a narrow set of read-only shell commands (ls, pwd, cat, "
            "git status/log/diff, python --version). Anything else will be rejected."
        ),
        tools=[client.get_shell_tool(func=shell.as_function())],
    )

    query = "Summarise the current directory and print the Python version."
    print(f"User: {query}")
    result = await agent.run(query)
    print(f"Agent: {result.text}")

Warning

Kebijakan perintah adalah penyaring awal untuk kemudahan penggunaan, bukan batas keamanan. Sintaks shell, alias, variabel, interpreter, dan payload yang dikodekan dapat melewati pencocokan pola sederhana.

Tambahkan ShellEnvironmentProvider

ShellEnvironmentProvider menyelidiki keluarga shell, versi, sistem operasi, direktori kerja, dan versi CLI yang dipilih, lalu menyuntikkan informasi tersebut sebelum agen berjalan. Daftar probe default adalah git, node, python, dan docker.

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)

Gunakan DockerShellTool

DockerShellTool memerlukan Docker atau Podman pada PATH. Default menonaktifkan jaringan, menjalankan sebagai pengguna non-root, menggunakan sistem file akar baca-saja, menghilangkan kemampuan, membatasi memori hingga 512 MiB, dan menutup kontainer pada 256 proses.

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)

Gambar defaultnya adalah mcr.microsoft.com/azurelinux/base/core:3.0. Gunakan docker_binary="podman" untuk menggunakan Podman. Contoh khusus yang dapat DockerShellTool dijalankan saat ini belum dipublikasikan.

Pilih tingkat eksekusi

Scenario Alat Batas isolasi
Perintah pengembangan tepercaya LocalShellTool Persetujuan di proses host
Perintah shell yang tidak tepercaya DockerShellTool Kontainer OCI dengan bendera isolasi default
Kode yang dihasilkan yang tidak tepercaya tanpa shell Hyperlight CodeAct Hyperlight microVM

Go menyediakan eksekusi shell lokal dan pemeriksaan lingkungan melalui tool/shelltool. Lihat Menggunakan alat shell lokal.

DockerShellTool panduan saat ini tidak tersedia untuk Go.

Gunakan alat shell dengan Harness Agent

Agen standar dan HarnessAgent menggunakan penyiapan shell yang terdiri dari dua bagian yang sama: daftarkan fungsi eksekutor sebagai alat, dan tambahkan ShellEnvironmentProvider saat model perlu menerima konteks shell, sistem operasi, direktori kerja, dan versi CLI. HarnessAgent tidak membuat atau memiliki eksekutor shell:

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 secara default menggunakan nama run_shell dan requireApproval: true. LocalShellExecutor secara default menggunakan mode persisten, batas maksimum 64 KiB per aliran output, dan tanpa batas waktu; contoh tersebut secara eksplisit menggunakan LocalShellExecutor.DefaultTimeout 30 detik yang direkomendasikan. ShellEnvironmentProviderOptions secara default memeriksa git, dotnet, node, python, dan docker, dengan batas waktu lima detik untuk setiap pemeriksaan.

Buat satu eksekutor persisten per sesi pengguna dan buang saat sesi berakhir. Jangan membagikannya di antara pengguna atau percakapan yang berlangsung secara bersamaan karena direktori kerja, variabel lingkungan, riwayat shell, proses latar belakang, dan antrean perintah digunakan bersama. ShellPolicy hanyalah pra-penyaring; tetap aktifkan persetujuan, gunakan kredensial dengan hak akses seminimal mungkin, dan utamakan DockerShellExecutor saat perintah memerlukan tingkat isolasi yang lebih kuat.

Perkakas shell tersedia dalam paket prarilis Microsoft.Agents.AI.Tools.Shell. HarnessAgent tersedia dari Microsoft.Agents.AI.Harness.

Untuk agen biasa, buat fungsi shell dengan client.get_shell_tool(func=shell.as_function()) dan tambahkan ShellEnvironmentProvider secara terpisah. create_harness_agent melakukan kedua langkah saat Anda meneruskan shell_executor:

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 bersifat opsional dan harus menampilkan as_function(). Factory menambahkan alat shell dan ShellEnvironmentProvider hanya jika klien mengimplementasikan SupportsShellTool; jika tidak, akan mencatat peringatan dan melewati keduanya. shell_environment_provider_options bersifat opsional dan hanya digunakan dengan shell_executor.

LocalShellTool secara default menggunakan mode persisten, batas waktu 30 detik, keluaran gabungan 64 KiB, penjangkaran ulang direktori kerja, dan approval_mode="always_require". Karena persetujuan alat Harness diaktifkan secara default, teruskan AgentSession ke run. Pemanggil memiliki siklus hidup pelaksana; gunakan async with atau panggil close(), dan buat satu alat persisten per sesi pengguna. Jangan berbagi status shell yang bisa diubah antarpengguna atau antarpercakapan yang berlangsung secara bersamaan.

Shell host bukan kotak pasir. Biarkan persetujuan tetap diaktifkan, gunakan kredensial dengan hak akses seminimal mungkin, dan gunakan DockerShellTool untuk isolasi kontainer. Menonaktifkan persetujuan memerlukan approval_mode="never_require" dan acknowledge_unsafe=True; ShellPolicy saja bukan batas keamanan.

create_harness_agent dirilis pada agent-framework-core. Integrasi shell didukung oleh paket prarilis agent-framework-tools dan menghasilkan ExperimentalWarning saat diaktifkan.

Paket Harness Go saat ini tidak tersedia. Buat alat shell lokal dan penyedia lingkungan langsung di agen Go biasa.